mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Math
synced 2024-12-23 21:02:51 +00:00
cd4ce78d31
Instead of manually crafted $this->testCase properties. Also make use of the dedicated PHPUnit feature to mark as test as passed. While assertTrue( true ) is a very common hack it can be confusing. Change-Id: Ic071bf561929c2a79537111bee18631865f91366
343 lines
11 KiB
PHP
343 lines
11 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Extension\Math\Tests\WikiTexVC;
|
|
|
|
use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLComparator;
|
|
use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLTestUtil;
|
|
use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLTestUtilHTML;
|
|
use MediaWiki\Extension\Math\WikiTexVC\TexUtil;
|
|
use MediaWiki\Extension\Math\WikiTexVC\TexVC;
|
|
use MediaWikiUnitTestCase;
|
|
|
|
/**
|
|
* This test is checking the MathML generation from LaTeX by WikiTexVC.
|
|
* It creates a list of basic LaTeX statements from the supported functions
|
|
* of WikiTexVC from TexUtil.php.
|
|
*
|
|
*
|
|
* For Re-Generating the testfile in case of change of macros in texutil.json:
|
|
*
|
|
* 1. Create a new ExportedTexUtilKeys.json by running this test with the ExportKeys flag
|
|
* 2. Create a new TexUtil-Ref file from the new ExportedKeys.json file with maintenances scripts, by running such cmd:
|
|
* "php extensions/Math/maintenance/JsonToMathML.php
|
|
* /var/www/html/extensions/Math/tests/phpunit/unit/WikiTexVC/ExportedTexUtilKeys.json
|
|
* /var/www/html/extensions/Math/TexUtil-Ref.json -i 2"
|
|
* 3. If generated correctly shift the TexUtil-Ref file in its usual folder
|
|
* WIP: This currently just generates MathML with WikiTexVC, but does not do
|
|
* a comparison.
|
|
*
|
|
* @covers \MediaWiki\Extension\Math\WikiTexVC\TexVC
|
|
*/
|
|
class MMLGenerationTexUtilTest extends MediaWikiUnitTestCase {
|
|
/** @var float */
|
|
private static $SIMILARITYTRESH = 0.7;
|
|
/** @var bool */
|
|
private static $SKIPXMLVALIDATION = true;
|
|
/** @var bool */
|
|
private static $APPLYFILTER = false;
|
|
/** @var bool */
|
|
private static $APPLYCATEGORYFILTER = false;
|
|
/** @var string[] */
|
|
private static $SKIPPEDCATEGORIES = [ "mhchemtexified_required" ];
|
|
/** @var int */
|
|
private static $FILTERSTART = 38;
|
|
/** @var int */
|
|
private static $FILTERLENGTH = 1;
|
|
|
|
/** @var bool */
|
|
private static $GENERATEHTML = false;
|
|
/** @var string */
|
|
private static $GENERATEDHTMLFILE = __DIR__ . "/MMLGenerationTexUtilTest-Output.html";
|
|
/** @var string */
|
|
private static $MMLREFFILE = __DIR__ . "/TexUtil-Ref.json";
|
|
|
|
/** @var bool export the updated TexUtil-Tex to "./ExportedTexUtilKeys.json" */
|
|
private static $EXPORT_KEYS = false;
|
|
|
|
/** @var int[] */
|
|
private static $SKIPPEDINDICES = [ 434, 489 ];
|
|
|
|
/**
|
|
* @dataProvider provideTestCases
|
|
*/
|
|
public function testTexVC( $title, $input ) {
|
|
if ( in_array( $input->ctr, self::$SKIPPEDINDICES, true ) ) {
|
|
MMLTestUtilHTML::generateHTMLtableRow( self::$GENERATEDHTMLFILE, [ $title, $input->tex, $input->mmlLaTeXML,
|
|
$input->mmlMathoid, "skipped", "skipped" ], false, self::$GENERATEHTML );
|
|
$this->addToAssertionCount( 1 );
|
|
return;
|
|
}
|
|
|
|
$texVC = new TexVC();
|
|
$useMHChem = self::getMHChem( $title ) || $input->type === "chem";
|
|
|
|
// Fetching the result from WikiTexVC
|
|
$resultT = $texVC->check( $input->tex, [
|
|
'debug' => false,
|
|
'usemathrm' => false,
|
|
'oldtexvc' => false,
|
|
'usemhchem' => $useMHChem,
|
|
'usemhchemtexified' => true
|
|
] );
|
|
|
|
$mathMLtexVC = isset( $resultT["input"] ) ? MMLTestUtil::getMMLwrapped( $resultT["input"] ) :
|
|
"<math> error texvc </math>";
|
|
|
|
$mmlComparator = new MMLComparator();
|
|
$usedMMLRef = $input->mmlMathoid ?? $input->mmlLaTeXML ?? "<math><merror> error no ref </merror></math>";
|
|
|
|
if ( !$usedMMLRef ) {
|
|
$usedMMLRef = $input->mmlLaTeXML;
|
|
}
|
|
$compRes = $mmlComparator->compareMathML( $usedMMLRef, $mathMLtexVC );
|
|
MMLTestUtilHTML::generateHTMLtableRow( self::$GENERATEDHTMLFILE, [ $title, $input->tex, $input->mmlLaTeXML ??
|
|
"no ref", $input->mmlMathoid ?? "no ref", $mathMLtexVC, $compRes['similarityF'] ],
|
|
false, self::$GENERATEHTML );
|
|
|
|
// Comparing the result either to MathML result from Mathoid
|
|
if ( !self::$SKIPXMLVALIDATION ) {
|
|
if ( $compRes['similarityF'] >= self::$SIMILARITYTRESH ) {
|
|
$this->addToAssertionCount( 1 );
|
|
} else {
|
|
$this->assertXmlStringEqualsXmlString( $usedMMLRef, $mathMLtexVC );
|
|
}
|
|
} else {
|
|
$this->addToAssertionCount( 1 );
|
|
}
|
|
}
|
|
|
|
private const SETS = [
|
|
'big_literals',
|
|
'box_functions',
|
|
'color_function',
|
|
'declh_function',
|
|
'definecolor_function',
|
|
'fun_ar1',
|
|
'fun_ar1nb',
|
|
'fun_ar1opt',
|
|
'fun_ar2',
|
|
'fun_ar2nb',
|
|
'fun_infix',
|
|
'fun_mhchem',
|
|
'hline_function',
|
|
'latex_function_names',
|
|
'left_function',
|
|
'mediawiki_function_names',
|
|
'mhchem_bond',
|
|
'mhchem_macro_1p',
|
|
'mhchem_macro_2p',
|
|
'mhchem_macro_2pc',
|
|
'mhchem_macro_2pu',
|
|
'mhchem_single_macro',
|
|
"mhchemtexified_required",
|
|
'nullary_macro',
|
|
'nullary_macro_in_mbox',
|
|
'other_delimiters1',
|
|
'other_delimiters2',
|
|
'right_function'
|
|
];
|
|
|
|
private const ARG_CNTS = [
|
|
"big_literals" => 1,
|
|
"box_functions" => 1,
|
|
"color_function" => 1,
|
|
"definecolor_function" => 1,
|
|
"fun_ar1" => 1,
|
|
"fun_ar1nb" => 1,
|
|
"fun_ar1opt" => 1,
|
|
"fun_ar2" => 2,
|
|
"fun_infix" => 1,
|
|
"fun_ar2nb" => 5,
|
|
"fun_mhchem" => 1,
|
|
"left_function" => 1,
|
|
"right_function" => 1,
|
|
"mhchem_bond" => 1,
|
|
"mhchem_macro_1p" => 1,
|
|
"mhchem_macro_2p" => 2,
|
|
"mhchem_macro_2pu" => 1
|
|
];
|
|
private const OTHER_ARGS = [
|
|
"declh_function" => true,
|
|
];
|
|
|
|
private const SAMPLE_ARGS_RIGHT = [
|
|
"big_literals" => '(',
|
|
"fun_ar2nb" => '{_1^2}{_3^4}\\sum',
|
|
"left_function" => '( \\right.',
|
|
"mhchem_bond" => '{-}',
|
|
"right_function" => ')',
|
|
|
|
];
|
|
|
|
private const SAMPLE_ARGS_LEFT = [
|
|
"right_function" => '\\left(',
|
|
];
|
|
|
|
private const ENTRY_ARGS = [
|
|
"\\atop" => "{ a \\atop b }",
|
|
"\\choose" => "{ a \\choose b }",
|
|
"\\over" => "{a \\over b }",
|
|
"\\color" => "a {b \\color{red} c} d",
|
|
"\\ce{\\color}" => "\\ce{a {b \\color{red} c} d}",
|
|
"\\definecolor" => "\\definecolor{ultramarine}{RGB}{0,32,96} a {b \\color{ultramarine} c} d",
|
|
"\\pagecolor" => "\\pagecolor{red} e^{i \\pi}",
|
|
"\\hline" => "\n\\begin{array}{|c||c|} a & b \\\\\n\\hline\n1&2 \n\\end{array}\n",
|
|
"\\nolimits" => " \mathop{\\rm cos}\\nolimits^2",
|
|
"\\llap" => "\\llap{40}",
|
|
"\\rlap" => "\\rlap{120}",
|
|
"\\smash" => "\\smash[t]{2}",
|
|
"\\lower" => "\\lower{1em}{-}",
|
|
"\\raise" => "\\raise{1em}{-}",
|
|
"\\mkern" => "\\mkern{3mu}",
|
|
"\\kern" => "\\kern{1.5mu}",
|
|
"\\mskip" => "\\mskip{2mu}",
|
|
"\\longLeftrightharpoons" => "\\longLeftrightharpoons{}",
|
|
"\\longRightleftharpoons" => "\\longRightleftharpoons{}",
|
|
"\\longleftrightarrows" => "\\longleftrightarrows{}",
|
|
"\\longrightleftharpoons" => "\\longleftrightarrows{}",
|
|
"\\tripledash" => "\\tripledash",
|
|
"\\mathchoice" => "\\mathchoice{a}{b}{c}{d}",
|
|
// "\\limits" =>" \mathop{\\rm cos}\\limits^2",
|
|
"\\limits" => "\\lim\\limits_{x \\to 2}",
|
|
"\\displaystyle" => "\\frac{\\displaystyle \\sum_{k=1}^N k^2}{a}",
|
|
"\\scriptscriptstyle" => "\\frac ab + \\scriptscriptstyle{\\frac cd + \\frac ef} + \\frac gh",
|
|
"\\scriptstyle" => "{\\scriptstyle \\partial \\Omega}",
|
|
"\\textstyle" => "\\textstyle \\sum_{k=1}^N k^2",
|
|
// Failing examples: ="\\vbox{{a}{b}}""\\vbox{\\vhb{eight}\\vhb{gnat}}"
|
|
// "\\vbox{\\hbox{eight}\\hbox{gnat}}";
|
|
"\\vbox" => "\\vbox{ab}",
|
|
"\\emph" => "\\mathit{\\emph{a}} \\emph{b}",
|
|
// it seems not supported for math, not in any other en_wiki test etc. probably make sense
|
|
// to drop or substitute with \\vert
|
|
"\\vline" => "\n\\begin{array}{|c||c|} a & b \\vline c \\\\\n\\hline\n1&2 \n\\end{array}\n",
|
|
];
|
|
|
|
/**
|
|
* Check from the test title if it is a mhchem-test.
|
|
* Return a boolean indicator for this.
|
|
* @param string $title test title
|
|
* @return bool indicator if the test is mhchem related
|
|
*/
|
|
public static function getMHChem( string $title ): bool {
|
|
$useMHChem = false;
|
|
if ( str_contains( $title, "chem" ) ) {
|
|
$useMHChem = true;
|
|
}
|
|
return $useMHChem;
|
|
}
|
|
|
|
public static function setUpBeforeClass(): void {
|
|
MMLTestUtilHTML::generateHTMLstart( self::$GENERATEDHTMLFILE, [ "name", "TeX-Input", "MathML(LaTeXML)",
|
|
"MathML(Mathoid)", "MathML(WikiTexVC)", "F-Similarity" ], self::$GENERATEHTML );
|
|
}
|
|
|
|
public static function tearDownAfterClass(): void {
|
|
MMLTestUtilHTML::generateHTMLEnd( self::$GENERATEDHTMLFILE, self::$GENERATEHTML );
|
|
}
|
|
|
|
/**
|
|
* Generate testcases with texutil, filter them and provide them to the testrunner.
|
|
* Fetch the corresponding reference MathML from the file defined as $MMLREFFILE
|
|
* @return array
|
|
*/
|
|
public static function provideTestCases() {
|
|
$refFileContent = (array)MMLTestUtil::getJSON( self::$MMLREFFILE );
|
|
$refAssociative = [];
|
|
foreach ( $refFileContent as $entry ) {
|
|
$refAssociative[$entry->tex] = $entry;
|
|
}
|
|
|
|
$groups = self::createGroups();
|
|
$overAllCtr = 0;
|
|
$finalCases = [];
|
|
foreach ( $groups as $category => $group ) {
|
|
if ( self::$APPLYCATEGORYFILTER && in_array( $category, self::$SKIPPEDCATEGORIES, true ) ) {
|
|
continue;
|
|
}
|
|
$indexCtr = 0;
|
|
foreach ( $group as $case ) {
|
|
$title = "set#" . $overAllCtr . ": " . $category . $indexCtr;
|
|
if ( $refAssociative[$case] ) {
|
|
$finalCase = $refAssociative[$case];
|
|
} else {
|
|
$type = str_starts_with( $case, "ce" ) ? "chem" : "tex";
|
|
$finalCase = (object)[ "tex" => $case, "type" => $type, "ctr" => null ];
|
|
}
|
|
|
|
if ( $category === "mhchemtexified_required" ) {
|
|
$finalCase->type = "chem";
|
|
}
|
|
$finalCase->ctr = $overAllCtr;
|
|
|
|
$finalCases[$title] = [ $title, $finalCase ];
|
|
$indexCtr++;
|
|
$overAllCtr++;
|
|
}
|
|
}
|
|
if ( self::$APPLYFILTER ) {
|
|
$finalCases = array_slice( $finalCases, self::$FILTERSTART, self::$FILTERLENGTH );
|
|
}
|
|
if ( self::$EXPORT_KEYS ) {
|
|
// Creating a reference file for lookup in JsonToMathML maintenance script.
|
|
$dataToExport = [];
|
|
foreach ( $finalCases as $case ) {
|
|
$dataToExport[$case[1]->tex] = $case[1]->type;
|
|
}
|
|
self::writeToFile( __DIR__ . "/ExportedTexUtilKeys.json", $dataToExport );
|
|
}
|
|
return $finalCases;
|
|
}
|
|
|
|
public static function writeToFile( string $fullPath, array $allEntries ): void {
|
|
$jsonData = json_encode( $allEntries, JSON_PRETTY_PRINT );
|
|
file_put_contents( $fullPath, $jsonData );
|
|
}
|
|
|
|
private static function addArgs( $set, $entry ) {
|
|
if ( isset( self::ENTRY_ARGS[$entry] ) ) {
|
|
// Some entries have specific mappings for non-group related arguments
|
|
if ( str_starts_with( $set, "mhchem" ) ) {
|
|
return '\\ce{' . self::ENTRY_ARGS[$entry] . '}';
|
|
} else {
|
|
return ( self::ENTRY_ARGS[$entry] );
|
|
}
|
|
}
|
|
$count = !isset( self::ARG_CNTS[$set] ) ? 0 : self::ARG_CNTS[$set];
|
|
$argsR = '';
|
|
$argsL = '';
|
|
if ( !isset( self::SAMPLE_ARGS_RIGHT[$set] ) ) {
|
|
for ( $i = 0; $i < $count; $i++ ) {
|
|
$argsR .= '{' . chr( 97 + $i ) . '}';
|
|
}
|
|
} else {
|
|
$argsR = self::SAMPLE_ARGS_RIGHT[$set];
|
|
}
|
|
if ( isset( self::SAMPLE_ARGS_LEFT[$set] ) ) {
|
|
$argsL = self::SAMPLE_ARGS_LEFT[$set];
|
|
}
|
|
if ( $argsR == '' && isset( self::OTHER_ARGS[$set] ) ) {
|
|
if ( self::OTHER_ARGS[$set] ) {
|
|
return "{" . $entry . " a }";
|
|
}
|
|
}
|
|
if ( str_starts_with( $set, "mhchem" ) ) {
|
|
$rendering = '\\ce{' . $argsL . $entry . $argsR . '}';
|
|
} else {
|
|
$rendering = $argsL . $entry . $argsR;
|
|
}
|
|
return $rendering;
|
|
}
|
|
|
|
private static function createGroups() {
|
|
$groups = [];
|
|
foreach ( self::SETS as $set ) {
|
|
$entries = array_keys( TexUtil::getInstance()->getBaseElements()[$set] );
|
|
foreach ( $entries as &$entry ) {
|
|
$entry = self::addArgs( $set, $entry );
|
|
}
|
|
$groups[$set] = $entries;
|
|
}
|
|
return $groups;
|
|
}
|
|
}
|