mediawiki-extensions-Visual.../tests/phpunit/integration/DirectParsoidClientTest.php
daniel 35cb550747 Local implementation of ParsoidClient (DirectParsoidClient)
* DirectParsoidClient makes use of parsoid directly for performing
transformations on both wikitext and/or HTML contents.

* Also, it's used to fetch HTML from parsoid's parser cache. Before,
this operation was done via RESTBase but now it's being fetched in
core's parsoid parser cache.

* This patch also enables VE clients to transform HTML to
Wikitext when switching from HTML to source mode on. It
makes use of the HtmlInputTransformHelper to perform this
transformation.

* Now, VE client can make use of core code for switching
between HTML to source mode and back without RESTBase.

Change-Id: I5c7cfcc4086d8da7905897194d8601aa07418b59
2022-10-11 18:34:06 +01:00

230 lines
6.9 KiB
PHP

<?php
namespace MediaWiki\Extension\VisualEditor\Tests;
use Generator;
use Language;
use MediaWiki\Extension\VisualEditor\DirectParsoidClient;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Parser\Parsoid\ParsoidOutputAccess;
use MediaWiki\Revision\RevisionRecord;
use MediaWikiIntegrationTestCase;
/**
* @coversDefaultClass \MediaWiki\Extension\VisualEditor\DirectParsoidClient
* @group Database
*/
class DirectParsoidClientTest extends MediaWikiIntegrationTestCase {
/**
* @return DirectParsoidClient
*/
private function createDirectClient(): DirectParsoidClient {
$services = $this->getServiceContainer();
$directClient = new DirectParsoidClient(
$services->getParsoidOutputStash(),
$services->getStatsdDataFactory(),
$services->getParsoidOutputAccess(),
$services->getHTMLTransformFactory(),
$services->getUserFactory()->newAnonymous()
);
return $directClient;
}
/** @return Generator */
public function provideLanguageCodes() {
yield 'German language code' => [ 'de' ];
yield 'English language code' => [ 'en' ];
yield 'French language code' => [ 'fr' ];
yield 'No language code, fallback to en' => [ null ];
}
private function createLanguage( $langCode, $allowNull = false ) {
if ( $langCode === null ) {
$language = $this->getServiceContainer()->getContentLanguage();
$langCode = $language->getCode();
if ( $allowNull ) {
$language = null;
}
} else {
$language = $this->createNoOpMock(
Language::class,
[ 'getCode' ]
);
$language->method( 'getCode' )->willReturn( $langCode );
}
return [ $language, $langCode ];
}
/**
* @covers ::getPageHtml
* @dataProvider provideLanguageCodes
*/
public function testGetPageHtml( $langCode ) {
$directClient = $this->createDirectClient();
$revision = $this->getExistingTestPage( 'DirectParsoidClient' )
->getRevisionRecord();
[ $language, $langCode ] = $this->createLanguage( $langCode, true );
$response = $directClient->getPageHtml( $revision, $language );
$pageHtml = $response['body'];
$headers = $response['headers'];
$this->assertIsArray( $response );
$this->assertArrayHasKey( 'body', $response );
$this->assertStringContainsString( 'DirectParsoidClient', $pageHtml );
$this->assertArrayHasKey( 'headers', $response );
$this->assertSame( $langCode, $headers['content-language'] );
$this->assertStringContainsString( 'lang="' . $langCode . '"', $pageHtml );
$this->assertArrayHasKey( 'etag', $headers );
$this->assertStringContainsString( (string)$revision->getId(), $headers['etag'] );
}
/**
* @covers ::transformHTML
* @dataProvider provideLanguageCodes
*/
public function testTransformHtml( $langCode ) {
$directClient = $this->createDirectClient();
$pageIdentity = PageIdentityValue::localIdentity(
1,
NS_MAIN,
'DirectParsoidClient'
);
[ $language, ] = $this->createLanguage( $langCode );
$html = '<h2>Hello World</h2>';
$oldid = $pageIdentity->getId();
$response = $directClient->transformHTML(
$pageIdentity,
$language,
$html,
$oldid,
// Supplying "null" will use the $oldid and look at recent rendering in ParserCache.
null
);
$this->assertIsArray( $response );
$this->assertArrayHasKey( 'headers', $response );
$this->assertArrayHasKey( 'Content-Type', $response['headers'] );
$this->assertArrayHasKey( 'body', $response );
// Trim to remove trailing newline
$wikitext = trim( $response['body'] );
$this->assertStringContainsString( '== Hello World ==', $wikitext );
}
/**
* @covers ::transformWikitext
* @dataProvider provideLanguageCodes
*/
public function testTransformWikitext( $langCode ) {
$directClient = $this->createDirectClient();
$page = $this->getExistingTestPage( 'DirectParsoidClient' );
$pageRecord = $page->toPageRecord();
$wikitext = '== Hello World ==';
[ $language, $langCode ] = $this->createLanguage( $langCode );
$response = $directClient->transformWikitext(
$pageRecord,
$language,
$wikitext,
false,
$pageRecord->getLatest(),
false
);
$this->assertIsArray( $response );
$this->assertArrayHasKey( 'body', $response );
$this->assertArrayHasKey( 'headers', $response );
$headers = $response['headers'];
$this->assertSame( $langCode, $headers['content-language'] );
$html = $response['body'];
$this->assertStringContainsString( $page->getTitle()->getText(), $html );
$this->assertStringContainsString( '>Hello World</h2>', $html );
}
/** @covers ::transformHTML */
public function testRoundTripSelserWithETag() {
$directClient = $this->createDirectClient();
// Nasty wikitext that would be reformated without selser.
$originalWikitext = '*a\n* b\n* <i>c</I>';
/** @var RevisionRecord $revision */
$revision = $this->editPage( 'RoundTripSelserWithETag', $originalWikitext )
->getValue()['revision-record'];
$pageHtmlResponse = $directClient->getPageHtml( $revision );
$eTag = $pageHtmlResponse['headers']['etag'];
$oldHtml = $pageHtmlResponse['body'];
$updatedHtml = str_replace( '</body>', '<p>More Text</p></body>', $oldHtml );
// Now make a new client object, so we can mock the ParsoidOutputAccess.
$parsoidOutputAccess = $this->createNoOpMock( ParsoidOutputAccess::class );
$services = $this->getServiceContainer();
$directClient = new DirectParsoidClient(
$services->getParsoidOutputStash(),
$services->getStatsdDataFactory(),
$parsoidOutputAccess,
$services->getHTMLTransformFactory(),
$services->getUserFactory()->newAnonymous()
);
[ $targetLanguage, ] = $this->createLanguage( 'en' );
$transformHtmlResponse = $directClient->transformHTML(
$revision->getPage(),
$targetLanguage,
$updatedHtml,
$revision->getId(),
$eTag
);
$updatedWikitext = $transformHtmlResponse['body'];
$this->assertStringContainsString( $originalWikitext, $updatedWikitext );
$this->assertStringContainsString( 'More Text', $updatedWikitext );
}
/** @covers ::transformHTML */
public function testRoundTripSelserWithoutETag() {
$directClient = $this->createDirectClient();
// Nasty wikitext that would be reformated without selser.
$originalWikitext = '*a\n* b\n* <i>c</I>';
/** @var RevisionRecord $revision */
$revision = $this->editPage( 'RoundTripSelserWithoutETag', $originalWikitext )
->getValue()['revision-record'];
$pageHtmlResponse = $directClient->getPageHtml( $revision );
$oldHtml = $pageHtmlResponse['body'];
$updatedHtml = str_replace( '</body>', '<p>More Text</p></body>', $oldHtml );
[ $targetLanguage, ] = $this->createLanguage( 'en' );
$transformHtmlResponse = $directClient->transformHTML(
$revision->getPage(),
$targetLanguage,
$updatedHtml,
$revision->getId(),
null
);
// Selser should still work, because the current rendering of the page still matches.
$updatedWikitext = $transformHtmlResponse['body'];
$this->assertStringContainsString( $originalWikitext, $updatedWikitext );
$this->assertStringContainsString( 'More Text', $updatedWikitext );
}
}