<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* END LICENSE
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*/
namespace seekquarry\yioop\tests;
use seekquarry\yioop\library\JavascriptUnitTest;
use seekquarry\yioop\library\WikiParser;
/**
* Checks that the Javascript wiki parser in scripts/wiki_parser.js renders wiki
* inputs to the same html the server-side WikiParser produces. The two
* parsers are a port of one another and are meant to agree character for
* character, so the server parser renders each input to get the html the
* Javascript parser is expected to match. There are two groups of cases:
* the code block cases Chris reported, where a multi-line code example used
* to leak its close code tag and a code block wrapping a pre tag used to
* break on its line breaks, and a broader group covering headings,
* emphasis, links, lists, tables, and other everyday markup. In a browser
* each group shows a pass or fail table; from the command line the
* JavascriptUnitTest runner replays the same check under node.
*
* @author Chris Pollett
*/
class WikiParserJavascriptTest extends JavascriptUnitTest
{
/**
* Checks the code and pre block cases Chris reported plus the plain
* inline code and pre cases they grew out of.
*
* @return string html and Javascript that checks each case in a
* browser or, from the command line, under node
*/
public function codeBlockTestCase()
{
$inputs = [
["inline code", "see <code>foo</code> here\n"],
["pre block", "<pre>x</pre>\n"],
["user agent code block",
"<code>\n Mozilla/5.0 (compatible; NAME_FROM_THIS_FIELD; " .
"YOUR_SITES_URL/bot)\n</code>\n"],
["code around pre on one line",
"<code><pre>lalala</pre></code>\n"],
["code around pre on many lines",
"<code>\n<pre>\nlalala\n</pre>\n</code>\n"],
["text after a pre close", "<pre>foo</pre> and more\n"],
];
return $this->renderCases("wikiParserCodeBlock",
"codeBlockTestCase", $inputs);
}
/**
* Checks a broad set of everyday wiki markup so the port is exercised
* beyond the code block cases.
*
* @return string html and Javascript that checks each case in a
* browser or, from the command line, under node
*/
public function parseCorpusTestCase()
{
$inputs = [
["heading", "== Sub =="],
["italic", "''i''"],
["bold", "'''b'''"],
["internal link", "[[Page|text]]"],
["external link", "[[http://x/|e]]"],
["nested list", "* a\n** b\n"],
["ordered list", "# one\n# two\n"],
["definition list", "; term : def\n"],
["table", "{|\n! H\n|-\n| c\n|}\n"],
["form text field", "{{textfield|Your Name|name}}"],
["form field with a break after it",
"{{r-textfield|Email:|Email}}<br>"],
["form field that must be filled",
"{{r-textfield|Your Name|name}}"],
["form writing box", "{{textarea|Your Story|story}}"],
["form check box", "{{checkbox|Agree|agree}}"],
["form choice", "{{radio|Yes|choice|yes}}"],
["form send button", "{{submit|Send it|send}}"],
["form list to pick from",
"{{dropdown|Pick one|choice}}\n{{option|First|1}}\n" .
"{{option|Second|2}}\n{{end-dropdown}}"],
["form list that must be picked from",
"{{r-dropdown|Pick one|choice}}\n{{option|First|1}}\n" .
"{{end-r-dropdown}}"],
["form list to put in order",
"{{sorter|Order these|order}}\n{{option|First|1}}\n" .
"{{end-sorter}}"],
["form list to pick some of",
"{{choosek|Pick two|picks|2}}\n{{option|First|1}}\n" .
"{{end-choosek}}"],
["form block of kept words",
"{{data-block|notes}}\nsome words\n{{end-data-block}}"],
["form check box with words after",
"{{lcheckbox|Agree|agree}}"],
["form choice with words after", "{{lradio|Yes|choice|yes}}"],
["form asking for a signed-in reader", "{{require-signin}}"],
["form kept secret", "{{secret-ballot}}"],
["form checked before sending", "{{presubmit|checkThem}}"],
["form acted on when sent", "{{onsubmit|sendThem}}"],
["form shared once edited", "{{share-edited-form}}"],
["form keyed to the reader", "{{user-record-form}}"],
["form keyed by a hash", "{{hash-record-form}}"],
["table holding a named file",
"{|\n|-\n! Head !! Head\n|-\n" .
"| ((resource-thumb:a.pdf|Some words)) || Example\n|}"],
["nowiki", "<nowiki><b>x</b></nowiki>\n"],
["indent", ": in\n"],
["horizontal rule", "----\n"],
["multibyte", "café ⚙\n"],
["block class", "{{block-class=\"nav\"\n* one\n* two\n}}"],
["block id", "{{block-id=\"top\"\nsome words\n}}"],
["block style",
"{{block-style=\"color:red\"\nsome words\n}}"],
["plain class", "{{class=\"nav\"\n* one\n* two\n}}"],
["centered block", "{{center|middle words}}"],
["block class with a stored quote",
"{{block-class="nav"\n* one\n* two\n}}"],
];
return $this->renderCases("wikiParserCorpus",
"parseCorpusTestCase", $inputs);
}
/**
* Checks how a file kept with a page is drawn in the preview of a
* page written in wiki markup. The site draws such a file after it
* has read the page rather than while reading it, so the parser
* class leaves the markup alone and the drawing is done by
* parseWikiContent. Each case here holds one form and what it
* should become: a picture, a picture standing in a link of its
* own, a picture drawn without any link, a document in a frame, and
* anything else as a link to it.
*
* @return string html and Javascript that checks the drawing in a
* browser or, from the command line, under node
*/
public function drawsFilesKeptWithPageTestCase()
{
ob_start();
?>
<div id="wikiResourceTest"></div>
<script src="../scripts/basic.js" ></script>
<script src="../scripts/wiki_parser.js" ></script>
<script>
var unit_test_results = [];
var drawnAs = function (name, source, wanted) {
var made = parseWikiContent(source, 7, 42, "group",
"YIOOP_TOKEN", "abc", true, "/");
return {name: name, pass: made.indexOf(wanted) >= 0 &&
made.indexOf("((resource") < 0};
};
unit_test_results.push(drawnAs("a picture is drawn as a picture",
"((resource:photo.webp|A photo))",
'<img src="/wd/resources/YIOOP_TOKEN=abc/7/42/photo.webp"'));
unit_test_results.push(drawnAs(
"a thumb stands in a link of its own",
"((resource-thumb:photo.webp|A photo))",
'<a class="image-list" href='));
unit_test_results.push(drawnAs(
"a thumb draws the picture kept beside the file",
"((resource-thumb:notes.pdf|The notes))",
'<img src="/wd/thumbs/YIOOP_TOKEN=abc/7/42/notes.pdf"'));
unit_test_results.push(drawnAs(
"a picture without a link is drawn on its own",
"((resource-nolink:photo.webp|A photo))",
'class="photo"'));
unit_test_results.push(drawnAs(
"a document is shown in a frame of its own",
"((resource:notes.pdf|The notes))",
'<iframe class="wiki-resource-object"'));
unit_test_results.push(drawnAs(
"a named file inside a table cell is drawn there",
"{|\n|-\n! Head !! Head\n|-\n" +
"| ((resource-thumb:a.pdf|Some words)) || Example\n|}",
'<td><a class="image-list"'));
/* A preview shows what a reader would see, so a fresh form
comes up empty rather than showing the marks the site fills
in when it draws the page. */
unit_test_results.push(drawnAs(
"a form field comes up empty for a reader",
"{{r-textfield|First Name:|first_name}}",
"value=''"));
unit_test_results.push({name:
"no standing-in marks are left in a drawn form",
pass: parseWikiContent("{{r-textfield|A|a}}\n{{checkbox|B|b}}",
7, 42, "group", "YIOOP_TOKEN", "abc", true,
"/").indexOf("[{") < 0});
/* The date, the reader's name and the moment ride along with a
form without being shown, as the site sends them. */
var carried = parseWikiContent("{{date}}", 7, 42, "group",
"YIOOP_TOKEN", "abc", true, "/");
unit_test_results.push({name:
"the date rides along without being shown",
pass: carried.indexOf("type='hidden' name='date'") >= 0 &&
carried.indexOf(new Date().getFullYear()) >= 0});
unit_test_results.push({name:
"the reader's name rides along under its own name",
pass: parseWikiContent("{{username|who}}", 7, 42, "group",
"YIOOP_TOKEN", "abc", true, "/").indexOf(
"name='who' value='[{username}]'") >= 0});
unit_test_results.push({name:
"the moment rides along as a count of seconds",
pass: (/name='at' value='\d{10}'/).test(parseWikiContent(
"{{timestamp|at}}", 7, 42, "group", "YIOOP_TOKEN", "abc",
true, "/"))});
/* A list takes its choices from a block of kept words named
beside it, which the page fills in once it is drawn. */
unit_test_results.push({name:
"a list names the block its choices come from",
pass: parseWikiContent("{{r-dropdown|Pick|YesOrNo}}\n" +
"{{end-r-dropdown}}", 7, 42, "group", "YIOOP_TOKEN",
"abc", true, "/").indexOf("data-block='YesOrNo'") >= 0});
unit_test_results.push({name:
"a field with words after it keeps them",
pass: parseWikiContent("{{r-textfield|Email:|Email}}<br>", 7,
42, "group", "YIOOP_TOKEN", "abc", true,
"/").indexOf("<br>") >= 0});
unit_test_results.push(drawnAs(
"anything else is a link to it",
"((resource-link:notes.txt|The notes))",
"<a href="));
</script>
<?php
$out_data = ob_get_contents();
ob_end_clean();
return $out_data;
}
/**
* Renders the server parser output for each input and builds the html
* and Javascript block that, in a browser or under node, renders each
* input again with the Javascript parser and reports whether it matches
* the server output. Each input is a name paired with a wiki source.
*
* @param string $container_id id of the div the result table is added
* under
* @param string $test_name name shown for this group of cases
* @param array $inputs list of name and wiki source pairs to check
* @return string the html and Javascript block for this group of cases
*/
private function renderCases($container_id, $test_name, $inputs)
{
$parser = new WikiParser("/b/");
$cases = [];
foreach ($inputs as $input) {
$cases[] = ["name" => $input[0], "source" => $input[1],
"expected" => $parser->parse($input[1], false, false, 0)];
}
$wiki_cases = json_encode($cases);
ob_start();
?>
<div id="<?= $container_id ?>">
</div>
<script src="../scripts/basic.js" ></script>
<script src="../scripts/wiki_parser.js" ></script>
<script src="../scripts/javascript_unit_test.js" ></script>
<script>
var wiki_cases = <?= $wiki_cases ?>;
var results = [];
for (let index = 0; index < wiki_cases.length; index++) {
let parser = new WikiParser("/b/", false);
let rendered = parser.parse(wiki_cases[index].source, false);
results.push({ name: wiki_cases[index].name,
pass: rendered === wiki_cases[index].expected });
}
reportUnitTestResults("<?= $container_id ?>", "<?= $test_name ?>",
results);
</script>
<?php
$out_data = ob_get_contents();
ob_end_clean();
return $out_data;
}
}