Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 109 |
|
0.00% |
0 / 15 |
CRAP | |
0.00% |
0 / 1 |
| SpecialMathStatus | |
0.00% |
0 / 109 |
|
0.00% |
0 / 15 |
1260 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| execute | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
56 | |||
| runNativeTest | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
| runMathMLTest | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| runMathLaTeXMLTest | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| testSpecialCaseText | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
| testMathMLIntegration | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
| testPmmlInput | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
| testLaTeXMLIntegration | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
| testLaTeXMLLinebreak | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 | |||
| assertTrue | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
| assertContains | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| assertEquals | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
| printDiff | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
| getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace MediaWiki\Extension\Math; |
| 4 | |
| 5 | use MediaWiki\Exception\UserNotLoggedIn; |
| 6 | use MediaWiki\Extension\Math\Render\RendererFactory; |
| 7 | use MediaWiki\Extension\Math\Widget\MathTestInputForm; |
| 8 | use MediaWiki\Logger\LoggerFactory; |
| 9 | use MediaWiki\Message\Message; |
| 10 | use MediaWiki\Registration\ExtensionRegistry; |
| 11 | use MediaWiki\SpecialPage\UnlistedSpecialPage; |
| 12 | use Psr\Log\LoggerInterface; |
| 13 | |
| 14 | /** |
| 15 | * MediaWiki math extension |
| 16 | * |
| 17 | * @copyright 2002-2015 Tomasz Wegrzanowski, Brooke Vibber, Moritz Schubotz, |
| 18 | * and other MediaWiki contributors |
| 19 | * @license GPL-2.0-or-later |
| 20 | * @author Moritz Schubotz |
| 21 | */ |
| 22 | class SpecialMathStatus extends UnlistedSpecialPage { |
| 23 | private readonly LoggerInterface $logger; |
| 24 | |
| 25 | public function __construct( |
| 26 | private readonly MathConfig $mathConfig, |
| 27 | private readonly RendererFactory $rendererFactory, |
| 28 | ) { |
| 29 | parent::__construct( 'MathStatus' ); |
| 30 | |
| 31 | $this->logger = LoggerFactory::getInstance( 'Math' ); |
| 32 | } |
| 33 | |
| 34 | /** |
| 35 | * @param null|string $query |
| 36 | */ |
| 37 | public function execute( $query ) { |
| 38 | $this->setHeaders(); |
| 39 | |
| 40 | if ( !( $this->getUser()->isNamed() ) ) { |
| 41 | // This page is primarily of interest to developers. |
| 42 | // This action is comparable to previewing or parsing a small snippet of wikitext. |
| 43 | // If using RESTBase instead of native MML, this page makes HTTP requests to it. |
| 44 | // Optimization: Avoid uncached math parsing for logged-out users. |
| 45 | throw new UserNotLoggedIn(); |
| 46 | } |
| 47 | |
| 48 | $out = $this->getOutput(); |
| 49 | $enabledMathModes = $this->mathConfig->getValidRenderingModeNames(); |
| 50 | $req = $this->getRequest(); |
| 51 | $tex = $req->getText( 'wptex' ); |
| 52 | |
| 53 | if ( $tex === '' ) { |
| 54 | $out->addWikiMsg( 'math-status-introduction', count( $enabledMathModes ) ); |
| 55 | |
| 56 | foreach ( $enabledMathModes as $modeNr => $modeName ) { |
| 57 | $out->wrapWikiMsg( '=== $1 ===', $modeName ); |
| 58 | $this->getOutput()->addWikiMsgArray( 'math-test-start', [ $modeName ] ); |
| 59 | switch ( $modeNr ) { |
| 60 | case MathConfig::MODE_MATHML: |
| 61 | $this->runMathMLTest(); |
| 62 | break; |
| 63 | case MathConfig::MODE_LATEXML: |
| 64 | $this->runMathLaTeXMLTest(); |
| 65 | break; |
| 66 | case MathConfig::MODE_NATIVE_MML: |
| 67 | $this->runNativeTest( $modeName ); |
| 68 | } |
| 69 | $this->getOutput()->addWikiMsgArray( 'math-test-end', [ $modeName ] ); |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | $form = new MathTestInputForm( $this, $enabledMathModes, $this->rendererFactory ); |
| 74 | $form->show(); |
| 75 | } |
| 76 | |
| 77 | private function runNativeTest( Message $modeMessage ): void { |
| 78 | $renderer = $this->rendererFactory->getRenderer( "a+b", [], MathConfig::MODE_NATIVE_MML ); |
| 79 | if ( !$this->assertTrue( $renderer->render(), 'Rendering of a+b in ' . $modeMessage->parse() ) ) { |
| 80 | return; |
| 81 | } |
| 82 | $real = str_replace( "\n", '', $renderer->getHtmlOutput() ); |
| 83 | $expected = '<mo stretchy="false">+</mo>'; |
| 84 | $this->assertContains( $expected, $real, "Checking the presence of '+' in the MathML output" ); |
| 85 | } |
| 86 | |
| 87 | private function runMathMLTest(): void { |
| 88 | $this->testSpecialCaseText(); |
| 89 | $this->testMathMLIntegration(); |
| 90 | $this->testPmmlInput(); |
| 91 | } |
| 92 | |
| 93 | private function runMathLaTeXMLTest(): void { |
| 94 | $this->testLaTeXMLIntegration(); |
| 95 | $this->testLaTeXMLLinebreak(); |
| 96 | } |
| 97 | |
| 98 | public function testSpecialCaseText() { |
| 99 | $renderer = $this->rendererFactory->getRenderer( 'x^2+\text{a sample Text}', [], MathConfig::MODE_MATHML ); |
| 100 | $expected = 'a sample Text</mtext>'; |
| 101 | if ( !$this->assertTrue( $renderer->render(), 'Rendering the input "x^2+\text{a sample Text}"' ) ) { |
| 102 | return; |
| 103 | } |
| 104 | $this->assertContains( |
| 105 | $expected, $renderer->getHtmlOutput(), 'Comparing to the reference rendering' |
| 106 | ); |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * Checks the basic functionality |
| 111 | * i.e. if the span element is generated right. |
| 112 | */ |
| 113 | public function testMathMLIntegration() { |
| 114 | $renderer = $this->rendererFactory->getRenderer( "a+b", [], MathConfig::MODE_MATHML ); |
| 115 | if ( !$this->assertTrue( $renderer->render(), "Rendering of a+b in plain MathML mode" ) ) { |
| 116 | return; |
| 117 | } |
| 118 | $real = str_replace( "\n", '', $renderer->getHtmlOutput() ); |
| 119 | $expected = '<mo>+</mo>'; |
| 120 | $this->assertContains( $expected, $real, "Checking the presence of '+' in the MathML output" ); |
| 121 | $this->assertContains( |
| 122 | '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ', |
| 123 | $renderer->getSvg(), |
| 124 | "Check that the generated SVG image contains the xlink namespace" |
| 125 | ); |
| 126 | } |
| 127 | |
| 128 | /** |
| 129 | * Checks the experimental option to 'render' MathML input |
| 130 | */ |
| 131 | public function testPmmlInput() { |
| 132 | // sample from 'Navajo Coal Combustion and Respiratory Health Near Shiprock, |
| 133 | // New Mexico' in ''Journal of Environmental and Public Health'' , vol. 2010p. |
| 134 | // authors Joseph E. Bunnell; Linda V. Garcia; Jill M. Furst; |
| 135 | // Harry Lerch; Ricardo A. Olea; Stephen E. Suitt; Allan Kolker |
| 136 | // phpcs:ignore Generic.Files.LineLength.TooLong |
| 137 | $inputSample = '<msub> <mrow> <mi> P</mi> </mrow> <mrow> <mi> i</mi> <mi> j</mi> </mrow> </msub> <mo> =</mo> <mfrac> <mrow> <mn> 100</mn> <msub> <mrow> <mi> d</mi> </mrow> <mrow> <mi> i</mi> <mi> j</mi> </mrow> </msub> </mrow> <mrow> <mn> 6.75</mn> <msub> <mrow> <mi> r</mi> </mrow> <mrow> <mi> j</mi> </mrow> </msub> </mrow> </mfrac> <mo> ,</mo> </math>'; |
| 138 | $attribs = [ 'type' => 'pmml' ]; |
| 139 | $renderer = $this->rendererFactory->getRenderer( $inputSample, $attribs, MathConfig::MODE_MATHML ); |
| 140 | $this->assertEquals( 'pmml', $renderer->getInputType(), 'Checking if MathML input is supported' ); |
| 141 | if ( !$this->assertTrue( $renderer->render(), 'Rendering Presentation MathML sample' ) ) { |
| 142 | return; |
| 143 | } |
| 144 | $real = $renderer->getHtmlOutput(); |
| 145 | $this->assertContains( 'mode=mathml', $real, 'Checking if the link to SVG image is in correct mode' ); |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * Checks the basic functionality |
| 150 | * i.e. if the span element is generated right. |
| 151 | */ |
| 152 | public function testLaTeXMLIntegration() { |
| 153 | $renderer = $this->rendererFactory->getRenderer( "a+b", [], MathConfig::MODE_LATEXML ); |
| 154 | if ( !$this->assertTrue( $renderer->render(), "Rendering of a+b in LaTeXML mode" ) ) { |
| 155 | return; |
| 156 | } |
| 157 | // phpcs:ignore Generic.Files.LineLength.TooLong |
| 158 | $expected = '<math xmlns="http://www.w3.org/1998/Math/MathML" '; |
| 159 | $real = preg_replace( "/\n\\s*/", '', $renderer->getHtmlOutput() ); |
| 160 | $this->assertContains( $expected, $real, |
| 161 | "Comparing the output to the MathML reference rendering" . |
| 162 | $renderer->getLastError() ); |
| 163 | } |
| 164 | |
| 165 | /** |
| 166 | * Checks LaTeXML line break functionality |
| 167 | * i.e. if a long line contains a mtr element. |
| 168 | * http://www.w3.org/TR/REC-MathML/chap3_5.html#sec3.5.2 |
| 169 | */ |
| 170 | public function testLaTeXMLLinebreak() { |
| 171 | $mathDefaultLaTeXMLSetting = $this->getConfig()->get( 'MathDefaultLaTeXMLSetting' ); |
| 172 | $tex = ''; |
| 173 | $testMax = ceil( $mathDefaultLaTeXMLSetting[ 'linelength' ] / 2 ); |
| 174 | for ( $i = 0; $i < $testMax; $i++ ) { |
| 175 | $tex .= "$i+"; |
| 176 | } |
| 177 | $tex .= $testMax; |
| 178 | $renderer = new MathLaTeXML( $tex, [ 'display' => 'linebreak' ] ); |
| 179 | $renderer->setPurge(); |
| 180 | if ( !$this->assertTrue( $renderer->render(), "Rendering of linebreak test in LaTeXML mode" ) ) { |
| 181 | return; |
| 182 | } |
| 183 | $expected = 'mtr'; |
| 184 | $real = preg_replace( "/\n\\s*/", '', $renderer->getHtmlOutput() ); |
| 185 | $this->assertContains( $expected, $real, "Checking for linebreak" . |
| 186 | $renderer->getLastError() ); |
| 187 | } |
| 188 | |
| 189 | private function assertTrue( bool $expression, string $message = '' ): bool { |
| 190 | if ( $expression ) { |
| 191 | $this->getOutput()->addWikiMsgArray( 'math-test-success', [ $message ] ); |
| 192 | return true; |
| 193 | } else { |
| 194 | $this->getOutput()->addWikiMsgArray( 'math-test-fail', [ $message ] ); |
| 195 | return false; |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | private function assertContains( string $expected, string $real, string $message = '' ) { |
| 200 | if ( !$this->assertTrue( strpos( $real, $expected ) !== false, $message ) ) { |
| 201 | $this->printDiff( $expected, $real, 'math-test-contains-diff' ); |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | /** |
| 206 | * @param array|string $expected |
| 207 | * @param array|string $real |
| 208 | * @param string $message |
| 209 | */ |
| 210 | private function assertEquals( $expected, $real, string $message = '' ): bool { |
| 211 | if ( is_array( $expected ) ) { |
| 212 | foreach ( $expected as $alternative ) { |
| 213 | if ( $alternative === $real ) { |
| 214 | $this->getOutput()->addWikiMsgArray( 'math-test-success', [ $message ] ); |
| 215 | return true; |
| 216 | } |
| 217 | } |
| 218 | // non of the alternatives matched |
| 219 | $this->getOutput()->addWikiMsgArray( 'math-test-fail', [ $message ] ); |
| 220 | return false; |
| 221 | } |
| 222 | if ( !$this->assertTrue( $expected === $real, $message ) ) { |
| 223 | $this->printDiff( $expected, $real, 'math-test-equals-diff' ); |
| 224 | return false; |
| 225 | } |
| 226 | return true; |
| 227 | } |
| 228 | |
| 229 | private function printDiff( string $expected, string $real, string $message = '' ) { |
| 230 | if ( ExtensionRegistry::getInstance()->isLoaded( "SyntaxHighlight" ) ) { |
| 231 | $expected = "<syntaxhighlight lang=\"xml\">$expected</syntaxhighlight>"; |
| 232 | $real = "<syntaxhighlight lang=\"xml\">$real</syntaxhighlight>"; |
| 233 | $this->getOutput()->addWikiMsgArray( $message, [ $real, $expected ] ); |
| 234 | } else { |
| 235 | $this->logger->warning( 'Can not display expected and real value.' . |
| 236 | 'SyntaxHighlight is not installed.' ); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | protected function getGroupName(): string { |
| 241 | return 'other'; |
| 242 | } |
| 243 | } |