Merge "mediawiki.api.watch: Use formatversion=2 for API requests"
[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 provideMakeResourceLoaderLink() {
139 // @codingStandardsIgnoreStart Generic.Files.LineLength
140 return [
141 // Single only=scripts load
142 [
143 [ 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS ],
144 "<script>(window.RLQ=window.RLQ||[]).push(function(){"
145 . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback");'
146 . "});</script>"
147 ],
148 // Multiple only=styles load
149 [
150 [ [ 'test.baz', 'test.foo', 'test.bar' ], ResourceLoaderModule::TYPE_STYLES ],
151
152 '<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"/>'
153 ],
154 // Private embed (only=scripts)
155 [
156 [ 'test.quux', ResourceLoaderModule::TYPE_SCRIPTS ],
157 "<script>(window.RLQ=window.RLQ||[]).push(function(){"
158 . "mw.test.baz({token:123});mw.loader.state({\"test.quux\":\"ready\"});"
159 . "});</script>"
160 ],
161 ];
162 // @codingStandardsIgnoreEnd
163 }
164
165 /**
166 * See ResourceLoaderClientHtmlTest for full coverage.
167 *
168 * @dataProvider provideMakeResourceLoaderLink
169 * @covers OutputPage::makeResourceLoaderLink
170 */
171 public function testMakeResourceLoaderLink( $args, $expectedHtml ) {
172 $this->setMwGlobals( [
173 'wgResourceLoaderDebug' => false,
174 'wgLoadScript' => 'http://127.0.0.1:8080/w/load.php',
175 ] );
176 $class = new ReflectionClass( 'OutputPage' );
177 $method = $class->getMethod( 'makeResourceLoaderLink' );
178 $method->setAccessible( true );
179 $ctx = new RequestContext();
180 $ctx->setSkin( SkinFactory::getDefaultInstance()->makeSkin( 'fallback' ) );
181 $ctx->setLanguage( 'en' );
182 $out = new OutputPage( $ctx );
183 $rl = $out->getResourceLoader();
184 $rl->setMessageBlobStore( new NullMessageBlobStore() );
185 $rl->register( [
186 'test.foo' => new ResourceLoaderTestModule( [
187 'script' => 'mw.test.foo( { a: true } );',
188 'styles' => '.mw-test-foo { content: "style"; }',
189 ] ),
190 'test.bar' => new ResourceLoaderTestModule( [
191 'script' => 'mw.test.bar( { a: true } );',
192 'styles' => '.mw-test-bar { content: "style"; }',
193 ] ),
194 'test.baz' => new ResourceLoaderTestModule( [
195 'script' => 'mw.test.baz( { a: true } );',
196 'styles' => '.mw-test-baz { content: "style"; }',
197 ] ),
198 'test.quux' => new ResourceLoaderTestModule( [
199 'script' => 'mw.test.baz( { token: 123 } );',
200 'styles' => '/* pref-animate=off */ .mw-icon { transition: none; }',
201 'group' => 'private',
202 ] ),
203 ] );
204 $links = $method->invokeArgs( $out, $args );
205 $actualHtml = strval( $links );
206 $this->assertEquals( $expectedHtml, $actualHtml );
207 }
208
209 /**
210 * @dataProvider provideVaryHeaders
211 * @covers OutputPage::addVaryHeader
212 * @covers OutputPage::getVaryHeader
213 * @covers OutputPage::getKeyHeader
214 */
215 public function testVaryHeaders( $calls, $vary, $key ) {
216 // get rid of default Vary fields
217 $outputPage = $this->getMockBuilder( 'OutputPage' )
218 ->setConstructorArgs( [ new RequestContext() ] )
219 ->setMethods( [ 'getCacheVaryCookies' ] )
220 ->getMock();
221 $outputPage->expects( $this->any() )
222 ->method( 'getCacheVaryCookies' )
223 ->will( $this->returnValue( [] ) );
224 TestingAccessWrapper::newFromObject( $outputPage )->mVaryHeader = [];
225
226 foreach ( $calls as $call ) {
227 call_user_func_array( [ $outputPage, 'addVaryHeader' ], $call );
228 }
229 $this->assertEquals( $vary, $outputPage->getVaryHeader(), 'Vary:' );
230 $this->assertEquals( $key, $outputPage->getKeyHeader(), 'Key:' );
231 }
232
233 public function provideVaryHeaders() {
234 // note: getKeyHeader() automatically adds Vary: Cookie
235 return [
236 [ // single header
237 [
238 [ 'Cookie' ],
239 ],
240 'Vary: Cookie',
241 'Key: Cookie',
242 ],
243 [ // non-unique headers
244 [
245 [ 'Cookie' ],
246 [ 'Accept-Language' ],
247 [ 'Cookie' ],
248 ],
249 'Vary: Cookie, Accept-Language',
250 'Key: Cookie,Accept-Language',
251 ],
252 [ // two headers with single options
253 [
254 [ 'Cookie', [ 'param=phpsessid' ] ],
255 [ 'Accept-Language', [ 'substr=en' ] ],
256 ],
257 'Vary: Cookie, Accept-Language',
258 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
259 ],
260 [ // one header with multiple options
261 [
262 [ 'Cookie', [ 'param=phpsessid', 'param=userId' ] ],
263 ],
264 'Vary: Cookie',
265 'Key: Cookie;param=phpsessid;param=userId',
266 ],
267 [ // Duplicate option
268 [
269 [ 'Cookie', [ 'param=phpsessid' ] ],
270 [ 'Cookie', [ 'param=phpsessid' ] ],
271 [ 'Accept-Language', [ 'substr=en', 'substr=en' ] ],
272 ],
273 'Vary: Cookie, Accept-Language',
274 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
275 ],
276 [ // Same header, different options
277 [
278 [ 'Cookie', [ 'param=phpsessid' ] ],
279 [ 'Cookie', [ 'param=userId' ] ],
280 ],
281 'Vary: Cookie',
282 'Key: Cookie;param=phpsessid;param=userId',
283 ],
284 ];
285 }
286
287 /**
288 * @covers OutputPage::haveCacheVaryCookies
289 */
290 function testHaveCacheVaryCookies() {
291 $request = new FauxRequest();
292 $context = new RequestContext();
293 $context->setRequest( $request );
294 $outputPage = new OutputPage( $context );
295
296 // No cookies are set.
297 $this->assertFalse( $outputPage->haveCacheVaryCookies() );
298
299 // 'Token' is present but empty, so it shouldn't count.
300 $request->setCookie( 'Token', '' );
301 $this->assertFalse( $outputPage->haveCacheVaryCookies() );
302
303 // 'Token' present and nonempty.
304 $request->setCookie( 'Token', '123' );
305 $this->assertTrue( $outputPage->haveCacheVaryCookies() );
306 }
307
308 /*
309 * @covers OutputPage::addCategoryLinks
310 * @covers OutputPage::getCategories
311 */
312 function testGetCategories() {
313 $fakeResultWrapper = new FakeResultWrapper( [
314 (object) [
315 'pp_value' => 1,
316 'page_title' => 'Test'
317 ],
318 (object) [
319 'page_title' => 'Test2'
320 ]
321 ] );
322 $outputPage = $this->getMockBuilder( 'OutputPage' )
323 ->setConstructorArgs( [ new RequestContext() ] )
324 ->setMethods( [ 'addCategoryLinksToLBAndGetResult' ] )
325 ->getMock();
326 $outputPage->expects( $this->any() )
327 ->method( 'addCategoryLinksToLBAndGetResult' )
328 ->will( $this->returnValue( $fakeResultWrapper ) );
329
330 $outputPage->addCategoryLinks( [
331 'Test' => 'Test',
332 'Test2' => 'Test2',
333 ] );
334 $this->assertEquals( [ 0 => 'Test', '1' => 'Test2' ], $outputPage->getCategories() );
335 $this->assertEquals( [ 0 => 'Test2' ], $outputPage->getCategories( 'normal' ) );
336 $this->assertEquals( [ 0 => 'Test' ], $outputPage->getCategories( 'hidden' ) );
337 }
338 }
339
340 /**
341 * MessageBlobStore that doesn't do anything
342 */
343 class NullMessageBlobStore extends MessageBlobStore {
344 public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
345 return [];
346 }
347
348 public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
349 return false;
350 }
351
352 public function updateModule( $name, ResourceLoaderModule $module, $lang ) {
353 }
354
355 public function updateMessage( $key ) {
356 }
357
358 public function clear() {
359 }
360 }