f0c8f7abc8f0e65dfd9dc0d3f94e1743371a2fe5
[lhc/web/wiklou.git] / tests / phpunit / includes / OutputPageTest.php
1 <?php
2
3 /**
4 *
5 * @author Matthew Flaschen
6 *
7 * @group Output
8 *
9 * @todo factor tests in this class into providers and test methods
10 */
11 class OutputPageTest extends MediaWikiTestCase {
12 const SCREEN_MEDIA_QUERY = 'screen and (min-width: 982px)';
13 const SCREEN_ONLY_MEDIA_QUERY = 'only screen and (min-width: 982px)';
14
15 /**
16 * Tests a particular case of transformCssMedia, using the given input, globals,
17 * expected return, and message
18 *
19 * Asserts that $expectedReturn is returned.
20 *
21 * options['printableQuery'] - value of query string for printable, or omitted for none
22 * options['handheldQuery'] - value of query string for handheld, or omitted for none
23 * options['media'] - passed into the method under the same name
24 * options['expectedReturn'] - expected return value
25 * options['message'] - PHPUnit message for assertion
26 *
27 * @param array $args Key-value array of arguments as shown above
28 */
29 protected function assertTransformCssMediaCase( $args ) {
30 $queryData = [];
31 if ( isset( $args['printableQuery'] ) ) {
32 $queryData['printable'] = $args['printableQuery'];
33 }
34
35 if ( isset( $args['handheldQuery'] ) ) {
36 $queryData['handheld'] = $args['handheldQuery'];
37 }
38
39 $fauxRequest = new FauxRequest( $queryData, false );
40 $this->setMwGlobals( [
41 'wgRequest' => $fauxRequest,
42 ] );
43
44 $actualReturn = OutputPage::transformCssMedia( $args['media'] );
45 $this->assertSame( $args['expectedReturn'], $actualReturn, $args['message'] );
46 }
47
48 /**
49 * Tests print requests
50 * @covers OutputPage::transformCssMedia
51 */
52 public function testPrintRequests() {
53 $this->assertTransformCssMediaCase( [
54 'printableQuery' => '1',
55 'media' => 'screen',
56 'expectedReturn' => null,
57 'message' => 'On printable request, screen returns null'
58 ] );
59
60 $this->assertTransformCssMediaCase( [
61 'printableQuery' => '1',
62 'media' => self::SCREEN_MEDIA_QUERY,
63 'expectedReturn' => null,
64 'message' => 'On printable request, screen media query returns null'
65 ] );
66
67 $this->assertTransformCssMediaCase( [
68 'printableQuery' => '1',
69 'media' => self::SCREEN_ONLY_MEDIA_QUERY,
70 'expectedReturn' => null,
71 'message' => 'On printable request, screen media query with only returns null'
72 ] );
73
74 $this->assertTransformCssMediaCase( [
75 'printableQuery' => '1',
76 'media' => 'print',
77 'expectedReturn' => '',
78 'message' => 'On printable request, media print returns empty string'
79 ] );
80 }
81
82 /**
83 * Tests screen requests, without either query parameter set
84 * @covers OutputPage::transformCssMedia
85 */
86 public function testScreenRequests() {
87 $this->assertTransformCssMediaCase( [
88 'media' => 'screen',
89 'expectedReturn' => 'screen',
90 'message' => 'On screen request, screen media type is preserved'
91 ] );
92
93 $this->assertTransformCssMediaCase( [
94 'media' => 'handheld',
95 'expectedReturn' => 'handheld',
96 'message' => 'On screen request, handheld media type is preserved'
97 ] );
98
99 $this->assertTransformCssMediaCase( [
100 'media' => self::SCREEN_MEDIA_QUERY,
101 'expectedReturn' => self::SCREEN_MEDIA_QUERY,
102 'message' => 'On screen request, screen media query is preserved.'
103 ] );
104
105 $this->assertTransformCssMediaCase( [
106 'media' => self::SCREEN_ONLY_MEDIA_QUERY,
107 'expectedReturn' => self::SCREEN_ONLY_MEDIA_QUERY,
108 'message' => 'On screen request, screen media query with only is preserved.'
109 ] );
110
111 $this->assertTransformCssMediaCase( [
112 'media' => 'print',
113 'expectedReturn' => 'print',
114 'message' => 'On screen request, print media type is preserved'
115 ] );
116 }
117
118 /**
119 * Tests handheld behavior
120 * @covers OutputPage::transformCssMedia
121 */
122 public function testHandheld() {
123 $this->assertTransformCssMediaCase( [
124 'handheldQuery' => '1',
125 'media' => 'handheld',
126 'expectedReturn' => '',
127 'message' => 'On request with handheld querystring and media is handheld, returns empty string'
128 ] );
129
130 $this->assertTransformCssMediaCase( [
131 'handheldQuery' => '1',
132 'media' => 'screen',
133 'expectedReturn' => null,
134 'message' => 'On request with handheld querystring and media is screen, returns null'
135 ] );
136 }
137
138 public static function provideTransformFilePath() {
139 $baseDir = dirname( __DIR__ ) . '/data/media';
140 return [
141 // File that matches basePath, and exists. Hash found and appended.
142 [ 'baseDir' => $baseDir, 'basePath' => '/w', '/w/test.jpg', '/w/test.jpg?edcf2' ],
143 // File that matches basePath, but not found on disk. Empty query.
144 [ 'baseDir' => $baseDir, 'basePath' => '/w', '/w/unknown.png', '/w/unknown.png?' ],
145 // File not matching basePath. Ignored.
146 [ 'baseDir' => $baseDir, 'basePath' => '/w', '/files/test.jpg' ],
147 // Empty string. Ignored.
148 [ 'baseDir' => $baseDir, 'basePath' => '/w', '', '' ],
149 // Similar path, but with domain component. Ignored.
150 [ 'baseDir' => $baseDir, 'basePath' => '/w', '//example.org/w/test.jpg' ],
151 [ 'baseDir' => $baseDir, 'basePath' => '/w', 'https://example.org/w/test.jpg' ],
152 // Unrelated path with domain component. Ignored.
153 [ 'baseDir' => $baseDir, 'basePath' => '/w', 'https://example.org/files/test.jpg' ],
154 [ 'baseDir' => $baseDir, 'basePath' => '/w', '//example.org/files/test.jpg' ],
155 ];
156 }
157
158 /**
159 * @dataProvider provideTransformFilePath
160 * @covers OutputPage::transformFilePath
161 * @covers OutputPage::transformResourcePath
162 */
163 public function testTransformResourcePath( $baseDir, $basePath, $path, $expected = null ) {
164 $this->setMwGlobals( 'IP', $baseDir );
165 $conf = new HashConfig( [ 'ResourceBasePath' => $basePath ] );
166
167 MediaWiki\suppressWarnings();
168 $actual = OutputPage::transformResourcePath( $conf, $path );
169 MediaWiki\restoreWarnings();
170
171 $this->assertEquals( $expected ?: $path, $actual );
172 }
173
174 public static function provideMakeResourceLoaderLink() {
175 // @codingStandardsIgnoreStart Generic.Files.LineLength
176 return [
177 // Single only=scripts load
178 [
179 [ 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS ],
180 "<script>(window.RLQ=window.RLQ||[]).push(function(){"
181 . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback");'
182 . "});</script>"
183 ],
184 // Multiple only=styles load
185 [
186 [ [ 'test.baz', 'test.foo', 'test.bar' ], ResourceLoaderModule::TYPE_STYLES ],
187
188 '<link rel="stylesheet" href="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.bar%2Cbaz%2Cfoo&amp;only=styles&amp;skin=fallback"/>'
189 ],
190 // Private embed (only=scripts)
191 [
192 [ 'test.quux', ResourceLoaderModule::TYPE_SCRIPTS ],
193 "<script>(window.RLQ=window.RLQ||[]).push(function(){"
194 . "mw.test.baz({token:123});mw.loader.state({\"test.quux\":\"ready\"});"
195 . "});</script>"
196 ],
197 ];
198 // @codingStandardsIgnoreEnd
199 }
200
201 /**
202 * See ResourceLoaderClientHtmlTest for full coverage.
203 *
204 * @dataProvider provideMakeResourceLoaderLink
205 * @covers OutputPage::makeResourceLoaderLink
206 */
207 public function testMakeResourceLoaderLink( $args, $expectedHtml ) {
208 $this->setMwGlobals( [
209 'wgResourceLoaderDebug' => false,
210 'wgLoadScript' => 'http://127.0.0.1:8080/w/load.php',
211 ] );
212 $class = new ReflectionClass( 'OutputPage' );
213 $method = $class->getMethod( 'makeResourceLoaderLink' );
214 $method->setAccessible( true );
215 $ctx = new RequestContext();
216 $ctx->setSkin( SkinFactory::getDefaultInstance()->makeSkin( 'fallback' ) );
217 $ctx->setLanguage( 'en' );
218 $out = new OutputPage( $ctx );
219 $rl = $out->getResourceLoader();
220 $rl->setMessageBlobStore( new NullMessageBlobStore() );
221 $rl->register( [
222 'test.foo' => new ResourceLoaderTestModule( [
223 'script' => 'mw.test.foo( { a: true } );',
224 'styles' => '.mw-test-foo { content: "style"; }',
225 ] ),
226 'test.bar' => new ResourceLoaderTestModule( [
227 'script' => 'mw.test.bar( { a: true } );',
228 'styles' => '.mw-test-bar { content: "style"; }',
229 ] ),
230 'test.baz' => new ResourceLoaderTestModule( [
231 'script' => 'mw.test.baz( { a: true } );',
232 'styles' => '.mw-test-baz { content: "style"; }',
233 ] ),
234 'test.quux' => new ResourceLoaderTestModule( [
235 'script' => 'mw.test.baz( { token: 123 } );',
236 'styles' => '/* pref-animate=off */ .mw-icon { transition: none; }',
237 'group' => 'private',
238 ] ),
239 ] );
240 $links = $method->invokeArgs( $out, $args );
241 $actualHtml = strval( $links );
242 $this->assertEquals( $expectedHtml, $actualHtml );
243 }
244
245 /**
246 * @dataProvider provideVaryHeaders
247 * @covers OutputPage::addVaryHeader
248 * @covers OutputPage::getVaryHeader
249 * @covers OutputPage::getKeyHeader
250 */
251 public function testVaryHeaders( $calls, $vary, $key ) {
252 // get rid of default Vary fields
253 $outputPage = $this->getMockBuilder( 'OutputPage' )
254 ->setConstructorArgs( [ new RequestContext() ] )
255 ->setMethods( [ 'getCacheVaryCookies' ] )
256 ->getMock();
257 $outputPage->expects( $this->any() )
258 ->method( 'getCacheVaryCookies' )
259 ->will( $this->returnValue( [] ) );
260 TestingAccessWrapper::newFromObject( $outputPage )->mVaryHeader = [];
261
262 foreach ( $calls as $call ) {
263 call_user_func_array( [ $outputPage, 'addVaryHeader' ], $call );
264 }
265 $this->assertEquals( $vary, $outputPage->getVaryHeader(), 'Vary:' );
266 $this->assertEquals( $key, $outputPage->getKeyHeader(), 'Key:' );
267 }
268
269 public function provideVaryHeaders() {
270 // note: getKeyHeader() automatically adds Vary: Cookie
271 return [
272 [ // single header
273 [
274 [ 'Cookie' ],
275 ],
276 'Vary: Cookie',
277 'Key: Cookie',
278 ],
279 [ // non-unique headers
280 [
281 [ 'Cookie' ],
282 [ 'Accept-Language' ],
283 [ 'Cookie' ],
284 ],
285 'Vary: Cookie, Accept-Language',
286 'Key: Cookie,Accept-Language',
287 ],
288 [ // two headers with single options
289 [
290 [ 'Cookie', [ 'param=phpsessid' ] ],
291 [ 'Accept-Language', [ 'substr=en' ] ],
292 ],
293 'Vary: Cookie, Accept-Language',
294 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
295 ],
296 [ // one header with multiple options
297 [
298 [ 'Cookie', [ 'param=phpsessid', 'param=userId' ] ],
299 ],
300 'Vary: Cookie',
301 'Key: Cookie;param=phpsessid;param=userId',
302 ],
303 [ // Duplicate option
304 [
305 [ 'Cookie', [ 'param=phpsessid' ] ],
306 [ 'Cookie', [ 'param=phpsessid' ] ],
307 [ 'Accept-Language', [ 'substr=en', 'substr=en' ] ],
308 ],
309 'Vary: Cookie, Accept-Language',
310 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
311 ],
312 [ // Same header, different options
313 [
314 [ 'Cookie', [ 'param=phpsessid' ] ],
315 [ 'Cookie', [ 'param=userId' ] ],
316 ],
317 'Vary: Cookie',
318 'Key: Cookie;param=phpsessid;param=userId',
319 ],
320 ];
321 }
322
323 /**
324 * @covers OutputPage::haveCacheVaryCookies
325 */
326 public function testHaveCacheVaryCookies() {
327 $request = new FauxRequest();
328 $context = new RequestContext();
329 $context->setRequest( $request );
330 $outputPage = new OutputPage( $context );
331
332 // No cookies are set.
333 $this->assertFalse( $outputPage->haveCacheVaryCookies() );
334
335 // 'Token' is present but empty, so it shouldn't count.
336 $request->setCookie( 'Token', '' );
337 $this->assertFalse( $outputPage->haveCacheVaryCookies() );
338
339 // 'Token' present and nonempty.
340 $request->setCookie( 'Token', '123' );
341 $this->assertTrue( $outputPage->haveCacheVaryCookies() );
342 }
343
344 /*
345 * @covers OutputPage::addCategoryLinks
346 * @covers OutputPage::getCategories
347 */
348 public function testGetCategories() {
349 $fakeResultWrapper = new FakeResultWrapper( [
350 (object) [
351 'pp_value' => 1,
352 'page_title' => 'Test'
353 ],
354 (object) [
355 'page_title' => 'Test2'
356 ]
357 ] );
358 $outputPage = $this->getMockBuilder( 'OutputPage' )
359 ->setConstructorArgs( [ new RequestContext() ] )
360 ->setMethods( [ 'addCategoryLinksToLBAndGetResult' ] )
361 ->getMock();
362 $outputPage->expects( $this->any() )
363 ->method( 'addCategoryLinksToLBAndGetResult' )
364 ->will( $this->returnValue( $fakeResultWrapper ) );
365
366 $outputPage->addCategoryLinks( [
367 'Test' => 'Test',
368 'Test2' => 'Test2',
369 ] );
370 $this->assertEquals( [ 0 => 'Test', '1' => 'Test2' ], $outputPage->getCategories() );
371 $this->assertEquals( [ 0 => 'Test2' ], $outputPage->getCategories( 'normal' ) );
372 $this->assertEquals( [ 0 => 'Test' ], $outputPage->getCategories( 'hidden' ) );
373 }
374 }
375
376 /**
377 * MessageBlobStore that doesn't do anything
378 */
379 class NullMessageBlobStore extends MessageBlobStore {
380 public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
381 return [];
382 }
383
384 public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
385 return false;
386 }
387
388 public function updateModule( $name, ResourceLoaderModule $module, $lang ) {
389 }
390
391 public function updateMessage( $key ) {
392 }
393
394 public function clear() {
395 }
396 }