Provide search by title prefix for any category of lint error

Bug: T185685
Change-Id: Ib667fcf5b2b1e752fde297b32b8bbe37dceabc5a
This commit is contained in:
sbailey 2022-03-10 11:48:19 -08:00
parent fcf403208f
commit 79e825a466
5 changed files with 403 additions and 92 deletions

View file

@ -42,13 +42,18 @@
"linter-category-wikilink-in-extlink": "Links in links",
"linter-category-wikilink-in-extlink-desc": "These pages have wikilinks in external links that could be fixed",
"linter-desc": "Track lint errors from an external service and show them to users",
"linter-lints-for-single-page-desc": "Show all linter errors for a specific page",
"linter-lints-prefix-search-page-desc": "Search for pages with lint errors by page title prefix or complete title",
"linter-invalid-title": "Namespace and/or pagename not found or malformed",
"linter-form-exact-match": "Search for a specific page title",
"linter-form-prefix-match": "Search for all pages that match the title prefix",
"linter-form-exact-or-prefix": "Search for a specific page title, or all pages with a common title prefix",
"linter-form-namespace": "Namespace:",
"linter-form-title-prefix": "Title prefix:",
"linter-form-title-prefix": "Please enter a title prefix or complete title:",
"linter-heading-high-priority": "High priority",
"linter-heading-low-priority": "Low priority",
"linter-heading-medium-priority": "Medium priority",
"linter-namespace-invert-error": "The \"Invert selection\" checkbox conflicts with the \"all\" namespace criteria.",
"linter-namespace-mismatch": "The namespace specified in the drop-down list does not match the namespace in the provided title text.",
"linter-numerrors": "($1 {{PLURAL:$1|error|errors}})",
"linter-page-edit": "edit",
"linter-page-history": "history",
@ -74,6 +79,7 @@
"linter-pager-tidy-whitespace-bug-details": "Whitespace parsing bug",
"linter-pager-title-header": "Page title",
"linter-pager-unclosed-quotes-in-heading-details": "Unclosed quote which leaks out of the table of contents",
"linter-prefix-search-subpage": "Pages with lint errors matching title prefix or complete title: $1",
"linterrors-subpage": "Lint errors: $1",
"linterrors-summary": "<strong>Note:</strong> The counts for categories are not accurate counts, but are based on estimates.",
"multi-part-template-block": "Output not from a single template",

View file

@ -50,13 +50,18 @@
"linter-category-wikilink-in-extlink": "Name of lint error category. See [[:mw:Help:Lint errors/wikilink-in-extlink]]",
"linter-category-wikilink-in-extlink-desc": "Description of category\n\nSimilar messages:\n* {{msg-mw|Linter-category-bogus-image-options-desc}}\n* {{msg-mw|Linter-category-deletable-table-tag-desc}}\n* {{msg-mw|Linter-category-fostered-desc}}\netc.",
"linter-desc": "{{desc|name=Linter|url=https://www.mediawiki.org/wiki/Extension:Linter}}",
"linter-lints-for-single-page-desc": "Heading for form section that allows user to show all linter errors for a specific page",
"linter-invalid-title": "Error message for linter for single page - not found or malformed namespace and-or pagename",
"linter-lints-prefix-search-page-desc": "Heading for a form section that allows users to search by page title prefix or complete title, for a group of pages with lint errors",
"linter-invalid-title": "Error message for lint errors for single page - not found or malformed namespace and-or pagename",
"linter-form-exact-match": "Label for radio button for Search for a specific page title",
"linter-form-prefix-match": "Label for radio button for Search for all pages that match the title prefix",
"linter-form-exact-or-prefix": "Label for the radio button to search for a specific page or all pages with a common prefix",
"linter-form-namespace": "Label for select field on Special:LintErrors\n{{Identical|Namespace}}",
"linter-form-title-prefix": "Label for text field to specify a page title prefix select criteria",
"linter-form-title-prefix": "Label for text field to specify a page title prefix or complete title as selection criteria",
"linter-heading-high-priority": "Heading on [[Special:LintErrors]]",
"linter-heading-low-priority": "Heading on [[Special:LintErrors]]",
"linter-heading-medium-priority": "Heading on [[Special:LintErrors]]",
"linter-namespace-invert-error": "Error message for lint errors for title search where the invert selection checkbox conflicts with the 'all' namespace criteria",
"linter-namespace-mismatch": "Error message for lint errors for title search where the drop-down list selection of namespace and the namespace in title text are mismatched",
"linter-numerrors": "Shown after a category link to indicate how many errors are in that category. $1 is the number of errors, and can be used for PLURAL.\n{{Identical|Error}}",
"linter-page-edit": "Link text for edit link in {{msg-mw|linter-page-title-edit}}\n{{Identical|Edit}}",
"linter-page-history": "Link text for history link in {{msg-mw|linter-page-title-edit}}\n{{Identical|History}}",
@ -82,6 +87,7 @@
"linter-pager-tidy-whitespace-bug-details": "Table column heading. For information about the bug, see the commit message https://gerrit.wikimedia.org/r/#/c/371068 .",
"linter-pager-title-header": "Table column heading for the column with page titles in it\n{{Identical|Page title}}",
"linter-pager-unclosed-quotes-in-heading-details": "Table column heading. See [[:mw:Help:Lint errors/unclosed-quotes-in-heading]].",
"linter-prefix-search-subpage": "Page title for title prefix search, $1 is the title prefix or complete title entered by user",
"linterrors-subpage": "Page title for Special:LintErrors, $1 is the localized category description",
"linterrors-summary": "Summary message for Special:LintErrors",
"multi-part-template-block": "Table cell on [[Special:LintErrors]] indicating that content block is not produced by a single template",

View file

@ -66,17 +66,17 @@ class LintErrorsPager extends TablePager {
/**
* @var bool
*/
private $invertnamespace;
private $invertNamespace;
/**
* @var int|null
* @var bool
*/
private $pageId;
private $exactMatch;
/**
* @var string|null
* @var string
*/
private $titlePrefix;
private $title;
/**
* @param IContextSource $context
@ -84,43 +84,45 @@ class LintErrorsPager extends TablePager {
* @param LinkRenderer $linkRenderer
* @param CategoryManager $catManager
* @param int|null $namespace
* @param bool $invertnamespace
* @param int|null $pageId
* @param string $titlePrefix
* @param bool $invertNamespace
* @param bool $exactMatch
* @param string $title
*/
public function __construct( IContextSource $context, $category, LinkRenderer $linkRenderer,
CategoryManager $catManager, $namespace, $invertnamespace, $pageId = null, $titlePrefix = ''
CategoryManager $catManager, $namespace, $invertNamespace, $exactMatch, $title
) {
$this->category = $category;
$this->categoryManager = $catManager;
if ( $category !== null ) {
$this->categoryId = $catManager->getCategoryId( $this->category );
$this->pageId = null;
} else {
$this->categoryId = null;
$this->pageId = $pageId;
}
$this->linkRenderer = $linkRenderer;
$this->namespace = $namespace;
$this->invertnamespace = $invertnamespace;
$this->titlePrefix = $titlePrefix;
$this->invertNamespace = $invertNamespace;
$this->exactMatch = $exactMatch;
$this->title = $title;
$this->haveParserMigrationExt = ExtensionRegistry::getInstance()->isLoaded( 'ParserMigration' );
parent::__construct( $context );
}
/** @inheritDoc */
public function getQueryInfo() {
$conds = [];
if ( $this->categoryId !== null ) {
$conds = [ 'linter_cat' => $this->categoryId ];
} else {
$conds = [ 'linter_page' => $this->pageId ];
$conds[ 'linter_cat' ] = $this->categoryId;
}
if ( $this->namespace !== null ) {
$eq_op = $this->invertnamespace ? '!=' : '=';
$eq_op = $this->invertNamespace ? '!=' : '=';
$conds[] = "page_namespace $eq_op " . $this->mDb->addQuotes( $this->namespace );
}
if ( $this->titlePrefix !== '' ) {
$conds[] = 'page_title' . $this->mDb->buildLike( $this->titlePrefix, $this->mDb->anyString() );
if ( $this->exactMatch ) {
if ( $this->title !== '' ) {
$conds[] = "page_title = " . $this->mDb->addQuotes( $this->title );
}
} else {
$conds[] = 'page_title' . $this->mDb->buildLike( $this->title, $this->mDb->anyString() );
}
return [
@ -182,6 +184,7 @@ class LintErrorsPager extends TablePager {
} else {
$editAction = 'edit';
}
switch ( $name ) {
case 'title':
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
@ -279,10 +282,8 @@ class LintErrorsPager extends TablePager {
$names = [
'title' => $this->msg( 'linter-pager-title-header' )->text(),
];
if ( isset( $this->pageId ) ) {
$names['category'] = $this->msg( 'linter-pager-category-header' )->text();
}
if ( !$this->category ) {
$names['category'] = $this->msg( 'linter-pager-category-header' )->text();
$names['details'] = $this->msg( "linter-pager-details-header" )->text();
} elseif ( !$this->categoryManager->hasNoParams( $this->category ) ) {
$names['details'] = $this->msg( "linter-pager-{$this->category}-details" )->text();

View file

@ -22,9 +22,10 @@ namespace MediaWiki\Linter;
use Html;
use HTMLForm;
use MalformedTitleException;
use MediaWiki\MediaWikiServices;
use OutputPage;
use SpecialPage;
use Title;
class SpecialLintErrors extends SpecialPage {
@ -39,9 +40,14 @@ class SpecialLintErrors extends SpecialPage {
/**
* @param int|null $ns
* @param bool $nsinvert
* @param bool $invert
* @param string $titleLabel
*/
protected function showNamespaceFilterForm( $ns, $nsinvert ) {
protected function showFilterForm( $ns, $invert, $titleLabel ) {
$selectOptions = [
(string)$this->msg( 'linter-form-exact-match' )->escaped() => true,
(string)$this->msg( 'linter-form-prefix-match' )->escaped() => false,
];
$fields = [
'namespace' => [
'type' => 'namespaceselect',
@ -52,83 +58,141 @@ class SpecialLintErrors extends SpecialPage {
'all' => '',
'cssclass' => 'namespaceselector'
],
'nsinvert' => [
'invertnamespace' => [
'type' => 'check',
'name' => 'invert',
'label-message' => 'invert',
'default' => $nsinvert,
'default' => $invert,
'tooltip' => 'invert'
],
'titleprefix' => [
'titlefield' => [
'type' => 'title',
'name' => 'titleprefix',
'name' => $titleLabel,
'label-message' => 'linter-form-title-prefix',
'exists' => true,
'required' => false
],
'exactmatchradio' => [
'type' => 'radio',
'name' => 'exactmatch',
'options' => $selectOptions,
'label-message' => 'linter-form-exact-or-prefix',
'default' => true
]
];
$form = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
$form->setWrapperLegend( true );
$form->addHeaderText( $this->msg( "linter-category-{$this->category}-desc" )->parse() );
if ( $this->category !== null ) {
$form->addHeaderText( $this->msg( "linter-category-{$this->category}-desc" )->parse() );
}
$form->setMethod( 'get' );
$form->prepareForm()->displayForm( false );
}
/**
* cleanTitle parses a title and handles a malformed titles, namespaces that are mismatched
* and exact title searches that find no matching records, and produce appropriate error messages
*
* @param string $title
* @param int|null $namespace
* @param bool $invert
* @return array
*/
protected function showPageNameFilterForm() {
$fields = [
'pagename' => [
'type' => 'title',
'name' => 'pagename',
'label-message' => 'linter-pager-title-header',
'exists' => true,
'required' => false,
]
];
$form = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
$form->setWrapperLegend( true );
$form->setMethod( 'get' );
$form->prepareForm()->displayForm( false );
public function cleanTitle( $title, $namespace, $invert ) {
// Check all titles for malformation regardless of exact match or prefix match
try {
$titleElements = MediaWikiServices::getInstance()->getTitleParser()->parseTitle( $title );
}
catch ( MalformedTitleException $e ) {
return [ 'titlefield' => null, 'error' => 'linter-invalid-title' ];
}
// The drop-down namespace defaults to 'all' which is returned as a null, indicating match all namespaces.
// If 'main' is selected in the drop-down, int 0 is returned. Other namespaces are returned as int values > 0.
//
// If the user does not specify a namespace in the title text box, parseTitle sets it to int 0 as the default.
// If the user entered ':' (main) namespace as the namespace prefix of a title such as ":MyPageTitle",
// parseTitle will also return int 0 as the namespace. Other valid system namespaces entered as prefixes
// in the title text box are returned by parseTitle as int values > 0.
// To determine if the user entered the ':' (main) namespace when int 0 is returned, a separate check for
// the substring ':' at offset 0 must be performed.
$titleNamespace = $titleElements->getNamespace();
// Determine if the user entered ':' (resolves to main) as the namespace part of the title,
// or was it was set by default by parseTitle() to 0, but the user intended to search across 'all' namespaces.
if ( $titleNamespace === 0 && $title[0] !== ':' ) {
$titleNamespace = null;
}
if ( $namespace === null && $titleNamespace === null ) {
// if invert checkbox is set while the namespace drop-down is set to 'all' and no namespace was set in the
// title text box, then the invert check box being set is invalid as it would exclude all namespaces.
if ( $invert ) {
return [ 'titlefield' => null, 'error' => 'linter-namespace-invert-error' ];
}
}
if ( $namespace !== null && $titleNamespace !== null ) {
// Show the namespace mismatch error if the namespaces specified in drop-down and title text do not match.
if ( $namespace !== $titleNamespace ) {
return [ 'titlefield' => null, 'error' => 'linter-namespace-mismatch' ];
}
}
// If the namespace drop-down selection is 'all' (null), return the namespace from the title text
$ns = ( $namespace === null ) ? $titleNamespace : $namespace;
return [ 'titlefield' => $titleElements->getDBkey(), 'namespace' => $ns ];
}
/**
* @param OutputPage $out
* @param string|null $message
*/
private function displayError( $out, $message ) {
$out->addHTML(
Html::element( 'span', [ 'class' => 'error' ],
$this->msg( $message )->text() )
);
}
/**
* @param string|null $par
*/
public function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
// If the request contains a `pagename` parameter, then the user entered a pagename
// and pressed the Submit button to display of all lints for a single page.
$params = $this->getRequest()->getQueryValues();
if ( $par === null && isset( $params['pagename'] ) ) {
// Use getText to ensure a string val
$pageName = $this->getRequest()->getText( 'pagename', '' );
$this->setHeaders();
$this->outputHeader( $par || isset( $params[ 'titlesearch' ] ) ? 'disable-summary' : '' );
$ns = $this->getRequest()->getIntOrNull( 'namespace' );
$invert = $this->getRequest()->getBool( 'invert' );
$exactMatch = $this->getRequest()->getBool( 'exactmatch', true );
// If the request contains a 'titlesearch' parameter, then the user entered a page title
// or just the first few characters of the title. They also may have entered the first few characters
// of a custom namespace (just text before a :) to search for and pressed the associated Submit button.
if ( $par === null && isset( $params[ 'titlesearch' ] ) ) {
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'linterrors-subpage', $pageName ) );
$title = Title::newFromText( $pageName );
if ( $title !== null ) {
$pageArticleID = $title->getArticleID();
if ( $pageArticleID !== 0 ) {
$ns = $title->getNamespace();
$catManager = new CategoryManager();
$pager = new LintErrorsPager(
$this->getContext(), null, $this->getLinkRenderer(),
$catManager, $ns, false, $pageArticleID
);
$out->addParserOutput( $pager->getFullOutput() );
return;
}
// No $pageArticleID or no $title go through
$out->addBacklinkSubtitle( $this->getPageTitle() );
$title = $this->getRequest()->getText( 'titlesearch' );
$titleSearch = $this->cleanTitle( $title, $ns, $invert );
if ( $titleSearch[ 'titlefield' ] !== null ) {
$out->setPageTitle( $this->msg( 'linter-prefix-search-subpage', $titleSearch[ 'titlefield' ] ) );
$catManager = new CategoryManager();
$pager = new LintErrorsPager(
$this->getContext(), null, $this->getLinkRenderer(),
$catManager, $titleSearch[ 'namespace' ], $invert, $exactMatch,
$titleSearch[ 'titlefield' ]
);
$out->addParserOutput( $pager->getFullOutput() );
} else {
$this->displayError( $out, $titleSearch[ 'error' ] );
}
$out->addHTML(
Html::element( 'span', [ 'class' => 'error' ],
$this->msg( "linter-invalid-title" )->text() )
);
return;
}
@ -149,18 +213,26 @@ class SpecialLintErrors extends SpecialPage {
)
);
$out->addBacklinkSubtitle( $this->getPageTitle() );
$ns = $this->getRequest()->getIntOrNull( 'namespace' );
$nsinvert = $this->getRequest()->getBool( 'invert' );
$titlePrefix = $this->getRequest()->getText( 'titleprefix', '' );
// SQL injection risk is handled in LintErrorsPager.php, getQueryInfo( using buildLike( db function
$title = $this->getRequest()->getText( 'titlecategorysearch' );
// For category based searches, allow an undefined title to display all records
if ( !isset( $params['titlecategorysearch'] ) && $title === '' ) {
$titleCategorySearch = [ 'titlefield' => '', 'namespace' => $ns, 'pageid' => null ];
} else {
$titleCategorySearch = $this->cleanTitle( $title, $ns, $invert );
}
$this->showNamespaceFilterForm( $ns, $nsinvert );
$pager = new LintErrorsPager(
$this->getContext(), $this->category, $this->getLinkRenderer(),
$catManager, $ns, $nsinvert, null, $titlePrefix
);
$out->addParserOutput( $pager->getFullOutput() );
if ( $titleCategorySearch[ 'titlefield' ] !== null ) {
$this->showFilterForm( null, $invert, 'titlecategorysearch' );
$pager = new LintErrorsPager(
$this->getContext(), $this->category, $this->getLinkRenderer(),
$catManager, $titleCategorySearch[ 'namespace' ], $invert, $exactMatch,
$titleCategorySearch[ 'titlefield' ]
);
$out->addParserOutput( $pager->getFullOutput() );
} else {
$this->displayError( $out, $titleCategorySearch[ 'error' ] );
}
}
}
@ -181,8 +253,8 @@ class SpecialLintErrors extends SpecialPage {
private function displaySearchPage() {
$out = $this->getOutput();
$out->addHTML( Html::element( 'h2', [],
$this->msg( "linter-lints-for-single-page-desc" )->text() ) );
$this->showPageNameFilterForm();
$this->msg( "linter-lints-prefix-search-page-desc" )->text() ) );
$this->showFilterForm( null, false, 'titlesearch' );
}
/**

View file

@ -21,6 +21,7 @@
namespace MediaWiki\Linter\Test;
use ContentHandler;
use FauxRequest;
use MediaWiki\Linter\CategoryManager;
use MediaWiki\Linter\Database;
use MediaWiki\Linter\LintError;
@ -53,14 +54,17 @@ class SpecialLintErrorsTest extends \SpecialPageTestBase {
}
/**
* @param string $titleText
* @param int|null $ns
* @return array
*/
private function createTitleAndPage() {
$titleText = 'TestPage';
private function createTitleAndPage( string $titleText = 'TestPage', ?int $ns = null ): array {
$userName = 'LinterUser';
$baseText = 'wikitext test content';
$ns = $this->getDefaultWikitextNS();
if ( $ns === null ) {
$ns = $this->getDefaultWikitextNS();
}
$title = Title::newFromText( $titleText, $ns );
$user = User::newFromName( $userName );
if ( $user->getId() === 0 ) {
@ -156,4 +160,226 @@ class SpecialLintErrorsTest extends \SpecialPageTestBase {
$errorsFromDb = array_values( $db->getForPage() );
$this->assertCount( 0, $errorsFromDb );
}
/**
* @param array $pageData
*/
private function createPagesWithLintErrorsFromData( array $pageData ) {
foreach ( $pageData as $data ) {
$titleAndPage = $this->createTitleAndPage( $data[ 'name' ], $data[ 'ns' ] );
$errors = [];
foreach ( $data[ 'lintErrors' ] as $lintError ) {
$errors[] = [
'type' => $lintError[ 'type' ],
'location' => $lintError[ 'location' ],
'params' => [],
'dbid' => null
];
}
$job = new RecordLintJob( $titleAndPage[ 'title' ], [
'errors' => $errors,
'revision' => $titleAndPage[ 'revID' ]
] );
$job->run();
}
}
/**
* @return array
*/
private function createTitleAndPageAndLintErrorData(): array {
$pageData = [];
$pageData[] = [ 'name' => 'Lint Error One', 'ns' => 0,
'lintErrors' => [ [ 'type' => 'obsolete-tag', 'location' => [ 0, 10 ] ],
[ 'type' => 'misnested-tag', 'location' => [ 20, 30 ] ] ]
];
$pageData[] = [ 'name' => 'LintErrorTwo', 'ns' => 3,
'lintErrors' => [ [ 'type' => 'obsolete-tag', 'location' => [ 0, 10 ] ] ]
];
$pageData[] = [ 'name' => 'NotANamespace:LintErrorThree', 'ns' => 0,
'lintErrors' => [ [ 'type' => 'obsolete-tag', 'location' => [ 0, 10 ] ],
[ 'type' => 'misnested-tag', 'location' => [ 20, 30 ] ] ]
];
$pageData[] = [ 'name' => 'NotANamespace:LintErrorFour', 'ns' => 0,
'lintErrors' => [ [ 'type' => 'obsolete-tag', 'location' => [ 0, 10 ] ],
[ 'type' => 'misnested-tag', 'location' => [ 20, 30 ] ] ]
];
$pageData[] = [ 'name' => 'Some other page', 'ns' => 0,
'lintErrors' => [ [ 'type' => 'bogus-image-options', 'location' => [ 30, 40 ] ] ]
];
$pageData[] = [ 'name' => 'FooBar:ErrorFive', 'ns' => 3,
'lintErrors' => [ [ 'type' => 'obsolete-tag', 'location' => [ 0, 10 ] ] ]
];
return $pageData;
}
// drop-down namespaces specified: all, Talk and User talk (defined in config as null, int 0, 1 and 3)
// namespace invert: true and false, some true cases make no sense like invert against all namespaces
// Titles exact matched and searched by tests include: empty - "", "L", "Lint Error One", "User Talk:L",
// "User talk:LintErrorTwo", "NotANamespace:L", "NotANamespace:LintErrorThree", "NotANamespace:LintErrorFour"
//
// Tests are grouped into three categories: empty title for all tests, namespace all for half and User talk for
// the rest, with invert and exact match booleans cycling.
//
// The second group is similar, the title being either "NotANamespace:L" or "NotANamespace:LintErrorThree" or
// "NotANamespace:LintErrorFour" with half namespace set all or User talk and invert and exact match booleans
// cycling.
//
// The third group is composed of tests against main, talk, User Talk and all namespaces. This test also includes
// titles with and without namespace prefixes, some which match the drop-down namespace and some which conflict
// depending on the invert setting and the combination of namespace definitions.
//
// The forth test covers the use of ':title' (main namespace) as the search text to ensure 'all' and 'main'
// are handled properly.
//
// The fifth test covers the user of an editor defined, non wiki defined namespace, without a namespace ID, but
// which was created in the User_talk wiki defined namespace ID 3.
//
// The sixth test covers accessing the search mechanism through the misnested-tag subpage. It verifies that
// LintErrorTwo, which has no misnested-tag errors is not in any search results, but other searches are as expected.
/**
* @param string|null $subpage
* @return array
*/
private function createLinterSearchTestConfigurations( ?string $subpage ): array {
$testConfigurations = [];
if ( $subpage !== 'misnested-tag' ) {
$testConfigurations[ 1 ] = [
'namespaces' => [ 0, 3 ],
'titles' => [ '' ],
'cases' => [ [ 'iterations' => [ 0, 1, 2, 3, 4, 5, 6, 7 ], 'message' => 'linter-invalid-title' ]
]
];
$testConfigurations[ 2 ] = [
'namespaces' => [ 0, 3 ],
'titles' => [ 'NotANamespace:L', 'NotANamespace:LintErrorFour' ],
'cases' => [
[ 'iterations' => [ 1, 11 ], 'message' => 'NotANamespace:LintErrorThree' ],
[ 'iterations' => [ 1, 4, 5, 11, 14, 15 ], 'message' => 'NotANamespace:LintErrorFour' ],
[ 'iterations' => [ 0, 2, 3, 6, 7, 8, 9, 10, 12, 13 ], 'message' => 'table_pager_empty' ]
]
];
$testConfigurations[ 3 ] = [
'namespaces' => [ 0, 1, 3, null ],
'titles' => [ 'L', 'Lint Error One', 'LintErrorTwo', 'User talk:L', 'User talk:LintErrorTwo',
'Talk:L' ],
'cases' => [
[ 'iterations' => [ 1, 4, 5, 27, 30, 31, 47, 51, 54, 55, 63, 73, 76, 77, 87, 95 ],
'message' => 'Lint Error One' ],
[ 'iterations' => [ 3, 10, 11, 27, 34, 35, 47, 49, 56, 57, 61, 64, 65, 73, 80, 81, 85, 88, 89, 95 ],
'message' => 'LintErrorTwo' ],
[ 'iterations' => [ 0, 2, 6, 7, 8, 9, 24, 25, 26, 28, 29, 32, 33, 44, 45, 46, 48, 50, 52, 53, 59,
58, 60, 62, 66, 67, 72, 84, 86, 90, 91, 92, 93, 94 ],
'message' => 'table_pager_empty' ],
[ 'iterations' => [ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 36, 37, 38, 39, 40, 41, 42,
43, 68, 69, 70, 71 ],
'message' => 'linter-namespace-mismatch' ],
[ 'iterations' => [ 74, 75, 78, 79, 82, 83 ],
'message' => 'linter-namespace-invert-error' ]
]
];
$testConfigurations[ 4 ] = [
'namespaces' => [ 0, 3, null ],
'titles' => [ ':Lint Error One' ],
'cases' => [
[ 'iterations' => [ 0, 1, 8, 9 ],
'message' => 'Lint Error One' ],
[ 'iterations' => [ 2, 3, 10, 11 ],
'message' => 'table_pager_empty' ],
[ 'iterations' => [ 4, 5, 6, 7 ],
'message' => 'linter-namespace-mismatch' ]
]
];
$testConfigurations[ 5 ] = [
'namespaces' => [ 0, 3, null ],
'titles' => [ 'FooBar:ErrorFive' ],
'cases' => [
[ 'iterations' => [ 2, 3, 4, 5, 8, 9 ],
'message' => 'FooBar:ErrorFive' ],
[ 'iterations' => [ 0, 1, 6, 7 ],
'message' => 'table_pager_empty' ],
[ 'iterations' => [ 10, 11 ],
'message' => 'linter-namespace-invert-error' ]
]
];
} else {
$testConfigurations[ 6 ] = [
'namespaces' => [ 0, 3 ],
'titles' => [ 'L', 'Lint Error One', "NotANamespace:L" ],
'cases' => [
[ 'iterations' => [ 1, 4, 5, 15, 18, 19 ], 'message' => 'title="Lint Error One">' ],
[ 'iterations' => [], 'message' => 'title="LintErrorTwo">' ],
[ 'iterations' => [ 9, 23 ], 'message' => 'title="NotANamespace:LintErrorThree">' ],
[ 'iterations' => [ 9, 23 ], 'message' => 'title="NotANamespace:LintErrorFour">' ],
[ 'iterations' => [ 0, 2, 3, 6, 7, 8, 10, 11, 12, 13, 14, 16, 17, 20, 21, 22 ],
'message' => '(table_pager_empty)' ]
]
];
}
return $testConfigurations;
}
/**
* @param array $testConfig
* @param string|null $subPage
* @param string $titleSearchString
* @return void
* @throws \Exception
*/
private function performLinterSearchTests( array $testConfig, ?string $subPage, string $titleSearchString ): void {
foreach ( $testConfig as $configIndex => $group ) {
$testIndex = 0;
foreach ( $group[ 'namespaces' ] as $namespace ) {
foreach ( $group[ 'titles' ] as $title ) {
$invert = false;
do {
$exact = true;
do {
$params = [ 'namespace' => $namespace, 'invert' => $invert,
$titleSearchString => $title, 'exactmatch' => $exact ];
$webRequest = new FauxRequest( $params );
$html = $this->executeSpecialPage( $subPage, $webRequest, 'qqx' )[ 0 ];
foreach ( $group[ 'cases' ] as $cases ) {
$invertString = [ 'unselected', 'selected' ][ $invert ];
$exactString = [ 'prefix', 'exact' ][ $exact ];
$message = $cases[ 'message' ];
$descriptionNamespace = $namespace === null ? 'all' : $namespace;
$description = "On config [$configIndex], iteration [$testIndex] " .
"namespace [$descriptionNamespace], invert checkbox [$invertString], " .
"for a [$exactString] match, with search title [$title], and test text [$message] ";
if ( in_array( $testIndex, $cases[ 'iterations' ] ) ) {
$this->assertStringContainsString( $message, $html, $description .
"was not found." );
} else {
$this->assertStringNotContainsString( $message, $html, $description .
"was not supposed to be found." );
}
}
$testIndex++;
$exact = !$exact;
} while ( !$exact );
$invert = !$invert;
} while ( $invert );
}
}
}
}
/**
* @throws \Exception
*/
public function testLinterSearchVariations(): void {
$this->createTitleAndPage();
$pageData = $this->createTitleAndPageAndLintErrorData();
$this->createPagesWithLintErrorsFromData( $pageData );
$testConfigurations = $this->createLinterSearchTestConfigurations( null );
$this->performLinterSearchTests( $testConfigurations, null, 'titlesearch' );
$testConfigurations = $this->createLinterSearchTestConfigurations( 'misnested-tag' );
$this->performLinterSearchTests( $testConfigurations, 'misnested-tag', 'titlecategorysearch' );
}
}