1<?php
2
3namespace Test\Parsoid\Html2Wt;
4
5use DOMDocument;
6use DOMElement;
7use PHPUnit\Framework\MockObject\MockObject;
8use PHPUnit\Framework\TestCase;
9use Wikimedia\Parsoid\Core\SelserData;
10use Wikimedia\Parsoid\Html2Wt\SerializerState;
11use Wikimedia\Parsoid\Html2Wt\WikitextSerializer;
12use Wikimedia\Parsoid\Mocks\MockEnv;
13use Wikimedia\Zest\Zest;
14
15class SerializerStateTest extends TestCase {
16
17	/**
18	 * A WikitextSerializer mock, with some basic methods mocked.
19	 * @param array $extraMethodsToMock
20	 * @return WikitextSerializer|MockObject
21	 */
22	private function getBaseSerializerMock( $extraMethodsToMock = [] ) {
23		$serializer = $this->getMockBuilder( WikitextSerializer::class )
24			->disableOriginalConstructor()
25			->setMethods( array_merge( [ 'buildSep', 'trace' ], $extraMethodsToMock ) )
26			->getMock();
27		$serializer->expects( $this->any() )
28			->method( 'buildSep' )
29			->willReturn( '' );
30		$serializer->expects( $this->any() )
31			->method( 'trace' )
32			->willReturn( null );
33		/** @var WikitextSerializer $serializer */
34		$serializer->env = new MockEnv( [] );
35		return $serializer;
36	}
37
38	private function getState(
39		array $options = [], MockEnv $env = null, WikitextSerializer $serializer = null
40	) {
41		if ( !$env ) {
42			$env = new MockEnv( [] );
43		}
44		if ( !$serializer ) {
45			$serializer = new WikitextSerializer( [ 'env' => $env ] );
46		}
47		return new SerializerState( $serializer, $options );
48	}
49
50	/**
51	 * Create a DOM document with the given HTML body and return the given node within it.
52	 * @param string $html
53	 * @param string $selector
54	 * @return DOMElement
55	 */
56	private function getNode( $html = '<div id="main"></div>', $selector = '#main' ) {
57		$document = new DOMDocument();
58		$document->loadHTML( "<html><body>$html</body></html>" );
59		return Zest::find( $selector, $document )[0];
60	}
61
62	/**
63	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::__construct
64	 */
65	public function testConstruct() {
66		$state = $this->getState();
67		$this->assertTrue( $state->rtTestMode );
68		$this->assertSame( [], $state->currLine->chunks );
69
70		$state = $this->getState( [ 'rtTestMode' => false ] );
71		$this->assertFalse( $state->rtTestMode );
72	}
73
74	/**
75	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::initMode
76	 */
77	public function testInitMode() {
78		$state = $this->getState();
79		$state->initMode( true );
80		$this->assertTrue( $state->selserMode );
81	}
82
83	/**
84	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::appendSep
85	 */
86	public function testAppendSep() {
87		$state = $this->getState();
88		$state->appendSep( 'foo' );
89		$state->appendSep( 'bar' );
90		$this->assertSame( 'foobar', $state->sep->src );
91	}
92
93	/**
94	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::updateSep
95	 */
96	public function testUpdateSep() {
97		$state = $this->getState();
98		$node = $this->getNode();
99		$state->updateSep( $node );
100		$this->assertSame( $node, $state->sep->lastSourceNode );
101	}
102
103	/**
104	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::getOrigSrc
105	 */
106	public function testGetOrigSrc() {
107		$env = new MockEnv( [] );
108		$selserData = new SelserData( '0123456789' );
109		$state = $this->getState( [
110			'selserData' => $selserData,
111		], $env );
112		$state->initMode( true );
113		$this->assertSame( '23', $state->getOrigSrc( 2, 4 ) );
114	}
115
116	/**
117	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::updateModificationFlags
118	 */
119	public function testUpdateModificationFlags() {
120		$state = $this->getState();
121		$node = $this->getNode();
122		$state->currNodeUnmodified = true;
123		$state->updateModificationFlags( $node );
124		$this->assertFalse( $state->currNodeUnmodified );
125	}
126
127	/**
128	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::emitChunk
129	 */
130	public function testEmitChunk() {
131		$serializer = $this->getBaseSerializerMock();
132		$serializer->env = new MockEnv( [] );
133		$state = $this->getState( [], null, $serializer );
134		$node = $this->getNode();
135		$this->assertSame( '', $state->currLine->text );
136		$state->emitChunk( 'foo', $node );
137		$this->assertSame( 'foo', $state->currLine->text );
138		$state->singleLineContext->enforce();
139		$state->emitChunk( "\nfoo", $node );
140		$this->assertSame( 'foo foo', $state->currLine->text );
141		$state->singleLineContext->pop();
142		$state->emitChunk( "\nfoo", $node );
143		$this->assertSame( "foo foo\nfoo", $state->currLine->text );
144		// FIXME this could be expanded a lot
145	}
146
147	/**
148	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::serializeChildren
149	 */
150	public function testSerializeChildren() {
151		$node = $this->getNode();
152		$serializer = $this->getBaseSerializerMock( [ 'serializeNode' ] );
153		$serializer->expects( $this->never() )
154			->method( 'serializeNode' );
155		$state = $this->getState( [], null, $serializer );
156		$state->serializeChildren( $node );
157
158		$node = $this->getNode( '<div id="main"><span></span><span></span></div>' );
159		$serializer = $this->getBaseSerializerMock( [ 'serializeNode' ] );
160		$serializer->expects( $this->exactly( 2 ) )
161			->method( 'serializeNode' )
162			->withConsecutive(
163				[ $node->firstChild ],
164				[ $node->firstChild->nextSibling ]
165			)
166			->willReturnCallback( function ( DOMElement $node ) {
167				return $node->nextSibling;
168			} );
169		$state = $this->getState( [], null, $serializer );
170		$state->serializeChildren( $node );
171
172		$callback = function () {
173		};
174		$node = $this->getNode( '<div id="main"><span></span></div>' );
175		$serializer = $this->getBaseSerializerMock( [ 'serializeNode' ] );
176		$serializer->expects( $this->once() )
177			->method( 'serializeNode' )
178			->with( $node->firstChild )
179			->willReturnCallback( function ( DOMElement $node ) use ( &$state, $callback ) {
180				$this->assertSame( $callback, end( $state->wteHandlerStack ) );
181				return $node->nextSibling;
182			} );
183		$state = $this->getState( [], null, $serializer );
184		$this->assertEmpty( $state->wteHandlerStack );
185		$state->serializeChildren( $node, $callback );
186		$this->assertEmpty( $state->wteHandlerStack );
187	}
188
189	/**
190	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::kickOffSerialize
191	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::serializeLinkChildrenToString
192	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::serializeCaptionChildrenToString
193	 * @covers \Wikimedia\Parsoid\Html2Wt\SerializerState::serializeIndentPreChildrenToString
194	 * @dataProvider provideSerializeChildrenToString
195	 */
196	public function testSerializeChildrenToString( $method ) {
197		$serializer = $this->getBaseSerializerMock( [ 'serializeNode' ] );
198		$serializer->expects( $this->never() )
199			->method( 'serializeNode' );
200		$state = $this->getState( [], null, $serializer );
201		$node = $this->getNode();
202		$callback = function () {
203		};
204		$state->$method( $node, $callback );
205	}
206
207	public function provideSerializeChildrenToString() {
208		return [
209			[ 'kickOffSerialize' ],
210			[ 'serializeLinkChildrenToString' ],
211			[ 'serializeCaptionChildrenToString' ],
212			[ 'serializeIndentPreChildrenToString' ],
213		];
214	}
215
216}
217