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