Security update for Core, with self-updated composer
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Asset / LibraryDiscoveryParserTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\Tests\Core\Asset\LibraryDiscoveryParserTest.
6  */
7
8 namespace Drupal\Tests\Core\Asset;
9
10 use Drupal\Core\Asset\Exception\IncompleteLibraryDefinitionException;
11 use Drupal\Core\Asset\Exception\InvalidLibraryFileException;
12 use Drupal\Core\Asset\Exception\LibraryDefinitionMissingLicenseException;
13 use Drupal\Core\Asset\LibraryDiscoveryParser;
14 use Drupal\Tests\UnitTestCase;
15
16 /**
17  * @coversDefaultClass \Drupal\Core\Asset\LibraryDiscoveryParser
18  * @group Asset
19  */
20 class LibraryDiscoveryParserTest extends UnitTestCase {
21
22   /**
23    * The tested library discovery parser service.
24    *
25    * @var \Drupal\Core\Asset\LibraryDiscoveryParser|\Drupal\Tests\Core\Asset\TestLibraryDiscoveryParser
26    */
27   protected $libraryDiscoveryParser;
28
29   /**
30    * The mocked cache backend.
31    *
32    * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
33    */
34   protected $cache;
35
36   /**
37    * The mocked module handler.
38    *
39    * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
40    */
41   protected $moduleHandler;
42
43   /**
44    * The mocked theme manager.
45    *
46    * @var \Drupal\Core\Theme\ThemeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
47    */
48   protected $themeManager;
49
50   /**
51    * The mocked lock backend.
52    *
53    * @var \Drupal\Core\Lock\LockBackendInterface|\PHPUnit_Framework_MockObject_MockObject
54    */
55   protected $lock;
56
57   /**
58    * {@inheritdoc}
59    */
60   protected function setUp() {
61     parent::setUp();
62
63     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
64     $this->themeManager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface');
65     $mock_active_theme = $this->getMockBuilder('Drupal\Core\Theme\ActiveTheme')
66       ->disableOriginalConstructor()
67       ->getMock();
68     $mock_active_theme->expects($this->any())
69       ->method('getLibrariesOverride')
70       ->willReturn([]);
71     $this->themeManager->expects($this->any())
72       ->method('getActiveTheme')
73       ->willReturn($mock_active_theme);
74     $this->libraryDiscoveryParser = new TestLibraryDiscoveryParser($this->root, $this->moduleHandler, $this->themeManager);
75   }
76
77   /**
78    * Tests that basic functionality works for getLibraryByName.
79    *
80    * @covers ::buildByExtension
81    */
82   public function testBuildByExtensionSimple() {
83     $this->moduleHandler->expects($this->atLeastOnce())
84       ->method('moduleExists')
85       ->with('example_module')
86       ->will($this->returnValue(TRUE));
87
88     $path = __DIR__ . '/library_test_files';
89     $path = substr($path, strlen($this->root) + 1);
90     $this->libraryDiscoveryParser->setPaths('module', 'example_module', $path);
91
92     $libraries = $this->libraryDiscoveryParser->buildByExtension('example_module');
93     $library = $libraries['example'];
94
95     $this->assertCount(0, $library['js']);
96     $this->assertCount(1, $library['css']);
97     $this->assertCount(0, $library['dependencies']);
98     $this->assertEquals($path . '/css/example.css', $library['css'][0]['data']);
99
100     // Ensures that VERSION is replaced by the current core version.
101     $this->assertEquals(\Drupal::VERSION, $library['version']);
102   }
103
104   /**
105    * Tests that a theme can be used instead of a module.
106    *
107    * @covers ::buildByExtension
108    */
109   public function testBuildByExtensionWithTheme() {
110     $this->moduleHandler->expects($this->atLeastOnce())
111       ->method('moduleExists')
112       ->with('example_theme')
113       ->will($this->returnValue(FALSE));
114
115     $path = __DIR__ . '/library_test_files';
116     $path = substr($path, strlen($this->root) + 1);
117     $this->libraryDiscoveryParser->setPaths('theme', 'example_theme', $path);
118
119     $libraries = $this->libraryDiscoveryParser->buildByExtension('example_theme');
120     $library = $libraries['example'];
121
122     $this->assertCount(0, $library['js']);
123     $this->assertCount(1, $library['css']);
124     $this->assertCount(0, $library['dependencies']);
125     $this->assertEquals($path . '/css/example.css', $library['css'][0]['data']);
126   }
127
128   /**
129    * Tests that a module with a missing library file results in FALSE.
130    *
131    * @covers ::buildByExtension
132    */
133   public function testBuildByExtensionWithMissingLibraryFile() {
134     $this->moduleHandler->expects($this->atLeastOnce())
135       ->method('moduleExists')
136       ->with('example_module')
137       ->will($this->returnValue(TRUE));
138
139     $path = __DIR__ . '/library_test_files_not_existing';
140     $path = substr($path, strlen($this->root) + 1);
141     $this->libraryDiscoveryParser->setPaths('module', 'example_module', $path);
142
143     $this->assertSame($this->libraryDiscoveryParser->buildByExtension('example_module'), []);
144   }
145
146   /**
147    * Tests that an exception is thrown when a libraries file couldn't be parsed.
148    *
149    * @covers ::buildByExtension
150    */
151   public function testInvalidLibrariesFile() {
152     $this->moduleHandler->expects($this->atLeastOnce())
153       ->method('moduleExists')
154       ->with('invalid_file')
155       ->will($this->returnValue(TRUE));
156
157     $path = __DIR__ . '/library_test_files';
158     $path = substr($path, strlen($this->root) + 1);
159     $this->libraryDiscoveryParser->setPaths('module', 'invalid_file', $path);
160
161     $this->setExpectedException(InvalidLibraryFileException::class);
162     $this->libraryDiscoveryParser->buildByExtension('invalid_file');
163   }
164
165   /**
166    * Tests that an exception is thrown when no CSS/JS/setting is specified.
167    *
168    * @covers ::buildByExtension
169    */
170   public function testBuildByExtensionWithMissingInformation() {
171     $this->moduleHandler->expects($this->atLeastOnce())
172       ->method('moduleExists')
173       ->with('example_module_missing_information')
174       ->will($this->returnValue(TRUE));
175
176     $path = __DIR__ . '/library_test_files';
177     $path = substr($path, strlen($this->root) + 1);
178     $this->libraryDiscoveryParser->setPaths('module', 'example_module_missing_information', $path);
179
180     $this->setExpectedException(IncompleteLibraryDefinitionException::class, "Incomplete library definition for definition 'example' in extension 'example_module_missing_information'");
181     $this->libraryDiscoveryParser->buildByExtension('example_module_missing_information');
182   }
183
184   /**
185    * Tests the version property, and how it propagates to the contained assets.
186    *
187    * @covers ::buildByExtension
188    */
189   public function testVersion() {
190     $this->moduleHandler->expects($this->atLeastOnce())
191       ->method('moduleExists')
192       ->with('versions')
193       ->will($this->returnValue(TRUE));
194
195     $path = __DIR__ . '/library_test_files';
196     $path = substr($path, strlen($this->root) + 1);
197     $this->libraryDiscoveryParser->setPaths('module', 'versions', $path);
198
199     $libraries = $this->libraryDiscoveryParser->buildByExtension('versions');
200
201     $this->assertFalse(array_key_exists('version', $libraries['versionless']));
202     $this->assertEquals(-1, $libraries['versionless']['css'][0]['version']);
203     $this->assertEquals(-1, $libraries['versionless']['js'][0]['version']);
204
205     $this->assertEquals('9.8.7.6', $libraries['versioned']['version']);
206     $this->assertEquals('9.8.7.6', $libraries['versioned']['css'][0]['version']);
207     $this->assertEquals('9.8.7.6', $libraries['versioned']['js'][0]['version']);
208
209     $this->assertEquals(\Drupal::VERSION, $libraries['core-versioned']['version']);
210     $this->assertEquals(\Drupal::VERSION, $libraries['core-versioned']['css'][0]['version']);
211     $this->assertEquals(\Drupal::VERSION, $libraries['core-versioned']['js'][0]['version']);
212   }
213
214
215   /**
216    * Tests that the version property of external libraries is handled.
217    *
218    * @covers ::buildByExtension
219    */
220   public function testExternalLibraries() {
221     $this->moduleHandler->expects($this->atLeastOnce())
222       ->method('moduleExists')
223       ->with('external')
224       ->will($this->returnValue(TRUE));
225
226     $path = __DIR__ . '/library_test_files';
227     $path = substr($path, strlen($this->root) + 1);
228     $this->libraryDiscoveryParser->setPaths('module', 'external', $path);
229
230     $libraries = $this->libraryDiscoveryParser->buildByExtension('external');
231     $library = $libraries['example_external'];
232
233     $this->assertEquals('http://example.com/css/example_external.css', $library['css'][0]['data']);
234     $this->assertEquals('http://example.com/example_external.js', $library['js'][0]['data']);
235     $this->assertEquals('3.14', $library['version']);
236   }
237
238   /**
239    * Ensures that CSS weights are taken into account properly.
240    *
241    * @covers ::buildByExtension
242    */
243   public function testDefaultCssWeights() {
244     $this->moduleHandler->expects($this->atLeastOnce())
245       ->method('moduleExists')
246       ->with('css_weights')
247       ->will($this->returnValue(TRUE));
248
249     $path = __DIR__ . '/library_test_files';
250     $path = substr($path, strlen($this->root) + 1);
251     $this->libraryDiscoveryParser->setPaths('module', 'css_weights', $path);
252
253     $libraries = $this->libraryDiscoveryParser->buildByExtension('css_weights');
254     $library = $libraries['example'];
255     $css = $library['css'];
256     $this->assertCount(10, $css);
257
258     // The following default weights are tested:
259     // - CSS_BASE: -200
260     // - CSS_LAYOUT: -100
261     // - CSS_COMPONENT: 0
262     // - CSS_STATE: 100
263     // - CSS_THEME: 200
264     $this->assertEquals(200, $css[0]['weight']);
265     $this->assertEquals(200 + 29, $css[1]['weight']);
266     $this->assertEquals(-200, $css[2]['weight']);
267     $this->assertEquals(-200 + 97, $css[3]['weight']);
268     $this->assertEquals(-100, $css[4]['weight']);
269     $this->assertEquals(-100 + 92, $css[5]['weight']);
270     $this->assertEquals(0, $css[6]['weight']);
271     $this->assertEquals(45, $css[7]['weight']);
272     $this->assertEquals(100, $css[8]['weight']);
273     $this->assertEquals(100 + 8, $css[9]['weight']);
274   }
275
276   /**
277    * Ensures that you cannot provide positive weights for JavaScript libraries.
278    *
279    * @covers ::buildByExtension
280    */
281   public function testJsWithPositiveWeight() {
282     $this->moduleHandler->expects($this->atLeastOnce())
283       ->method('moduleExists')
284       ->with('js_positive_weight')
285       ->will($this->returnValue(TRUE));
286
287     $path = __DIR__ . '/library_test_files';
288     $path = substr($path, strlen($this->root) + 1);
289     $this->libraryDiscoveryParser->setPaths('module', 'js_positive_weight', $path);
290
291     $this->setExpectedException(\UnexpectedValueException::class);
292     $this->libraryDiscoveryParser->buildByExtension('js_positive_weight');
293   }
294
295   /**
296    * Tests a library with CSS/JavaScript and a setting.
297    *
298    * @covers ::buildByExtension
299    */
300   public function testLibraryWithCssJsSetting() {
301     $this->moduleHandler->expects($this->atLeastOnce())
302       ->method('moduleExists')
303       ->with('css_js_settings')
304       ->will($this->returnValue(TRUE));
305
306     $path = __DIR__ . '/library_test_files';
307     $path = substr($path, strlen($this->root) + 1);
308     $this->libraryDiscoveryParser->setPaths('module', 'css_js_settings', $path);
309
310     $libraries = $this->libraryDiscoveryParser->buildByExtension('css_js_settings');
311     $library = $libraries['example'];
312
313     // Ensures that the group and type are set automatically.
314     $this->assertEquals(-100, $library['js'][0]['group']);
315     $this->assertEquals('file', $library['js'][0]['type']);
316     $this->assertEquals($path . '/js/example.js', $library['js'][0]['data']);
317
318     $this->assertEquals(0, $library['css'][0]['group']);
319     $this->assertEquals('file', $library['css'][0]['type']);
320     $this->assertEquals($path . '/css/base.css', $library['css'][0]['data']);
321
322     $this->assertEquals(['key' => 'value'], $library['drupalSettings']);
323   }
324
325   /**
326    * Tests a library with dependencies.
327    *
328    * @covers ::buildByExtension
329    */
330   public function testLibraryWithDependencies() {
331     $this->moduleHandler->expects($this->atLeastOnce())
332       ->method('moduleExists')
333       ->with('dependencies')
334       ->will($this->returnValue(TRUE));
335
336     $path = __DIR__ . '/library_test_files';
337     $path = substr($path, strlen($this->root) + 1);
338     $this->libraryDiscoveryParser->setPaths('module', 'dependencies', $path);
339
340     $libraries = $this->libraryDiscoveryParser->buildByExtension('dependencies');
341     $library = $libraries['example'];
342
343     $this->assertCount(2, $library['dependencies']);
344     $this->assertEquals('external/example_external', $library['dependencies'][0]);
345     $this->assertEquals('example_module/example', $library['dependencies'][1]);
346   }
347
348   /**
349    * Tests a library with a couple of data formats like full URL.
350    *
351    * @covers ::buildByExtension
352    */
353   public function testLibraryWithDataTypes() {
354     $this->moduleHandler->expects($this->atLeastOnce())
355       ->method('moduleExists')
356       ->with('data_types')
357       ->will($this->returnValue(TRUE));
358
359     $path = __DIR__ . '/library_test_files';
360     $path = substr($path, strlen($this->root) + 1);
361     $this->libraryDiscoveryParser->setPaths('module', 'data_types', $path);
362
363     $this->libraryDiscoveryParser->setFileValidUri('public://test.css', TRUE);
364     $this->libraryDiscoveryParser->setFileValidUri('public://test2.css', FALSE);
365
366     $libraries = $this->libraryDiscoveryParser->buildByExtension('data_types');
367     $library = $libraries['example'];
368
369     $this->assertCount(5, $library['css']);
370     $this->assertEquals('external', $library['css'][0]['type']);
371     $this->assertEquals('http://example.com/test.css', $library['css'][0]['data']);
372     $this->assertEquals('file', $library['css'][1]['type']);
373     $this->assertEquals('tmp/test.css', $library['css'][1]['data']);
374     $this->assertEquals('external', $library['css'][2]['type']);
375     $this->assertEquals('//cdn.com/test.css', $library['css'][2]['data']);
376     $this->assertEquals('file', $library['css'][3]['type']);
377     $this->assertEquals('public://test.css', $library['css'][3]['data']);
378   }
379
380   /**
381    * Tests a library with JavaScript-specific flags.
382    *
383    * @covers ::buildByExtension
384    */
385   public function testLibraryWithJavaScript() {
386     $this->moduleHandler->expects($this->atLeastOnce())
387       ->method('moduleExists')
388       ->with('js')
389       ->will($this->returnValue(TRUE));
390
391     $path = __DIR__ . '/library_test_files';
392     $path = substr($path, strlen($this->root) + 1);
393     $this->libraryDiscoveryParser->setPaths('module', 'js', $path);
394
395     $libraries = $this->libraryDiscoveryParser->buildByExtension('js');
396     $library = $libraries['example'];
397
398     $this->assertCount(2, $library['js']);
399     $this->assertEquals(FALSE, $library['js'][0]['minified']);
400     $this->assertEquals(TRUE, $library['js'][1]['minified']);
401   }
402   /**
403    * Tests that an exception is thrown when license is missing when 3rd party.
404    *
405    * @covers ::buildByExtension
406    */
407   public function testLibraryThirdPartyWithMissingLicense() {
408     $this->moduleHandler->expects($this->atLeastOnce())
409       ->method('moduleExists')
410       ->with('licenses_missing_information')
411       ->will($this->returnValue(TRUE));
412
413     $path = __DIR__ . '/library_test_files';
414     $path = substr($path, strlen($this->root) + 1);
415     $this->libraryDiscoveryParser->setPaths('module', 'licenses_missing_information', $path);
416
417     $this->setExpectedException(LibraryDefinitionMissingLicenseException::class, "Missing license information in library definition for definition 'no-license-info-but-remote' extension 'licenses_missing_information': it has a remote, but no license.");
418     $this->libraryDiscoveryParser->buildByExtension('licenses_missing_information');
419   }
420
421   /**
422    * Tests a library with various licenses, some GPL-compatible, some not.
423    *
424    * @covers ::buildByExtension
425    */
426   public function testLibraryWithLicenses() {
427     $this->moduleHandler->expects($this->atLeastOnce())
428       ->method('moduleExists')
429       ->with('licenses')
430       ->will($this->returnValue(TRUE));
431
432     $path = __DIR__ . '/library_test_files';
433     $path = substr($path, strlen($this->root) + 1);
434     $this->libraryDiscoveryParser->setPaths('module', 'licenses', $path);
435
436     $libraries = $this->libraryDiscoveryParser->buildByExtension('licenses');
437
438     // For libraries without license info, the default license is applied.
439     $library = $libraries['no-license-info'];
440     $this->assertCount(1, $library['css']);
441     $this->assertCount(1, $library['js']);
442     $this->assertTrue(isset($library['license']));
443     $default_license = [
444       'name' => 'GNU-GPL-2.0-or-later',
445       'url' => 'https://www.drupal.org/licensing/faq',
446       'gpl-compatible' => TRUE,
447     ];
448     $this->assertEquals($library['license'], $default_license);
449
450     // GPL2-licensed libraries.
451     $library = $libraries['gpl2'];
452     $this->assertCount(1, $library['css']);
453     $this->assertCount(1, $library['js']);
454     $expected_license = [
455       'name' => 'gpl2',
456       'url' => 'https://url-to-gpl2-license',
457       'gpl-compatible' => TRUE,
458     ];
459     $this->assertEquals($library['license'], $expected_license);
460
461     // MIT-licensed libraries.
462     $library = $libraries['mit'];
463     $this->assertCount(1, $library['css']);
464     $this->assertCount(1, $library['js']);
465     $expected_license = [
466       'name' => 'MIT',
467       'url' => 'https://url-to-mit-license',
468       'gpl-compatible' => TRUE,
469     ];
470     $this->assertEquals($library['license'], $expected_license);
471
472     // Libraries in the Public Domain.
473     $library = $libraries['public-domain'];
474     $this->assertCount(1, $library['css']);
475     $this->assertCount(1, $library['js']);
476     $expected_license = [
477       'name' => 'Public Domain',
478       'url' => 'https://url-to-public-domain-license',
479       'gpl-compatible' => TRUE,
480     ];
481     $this->assertEquals($library['license'], $expected_license);
482
483     // Apache-licensed libraries.
484     $library = $libraries['apache'];
485     $this->assertCount(1, $library['css']);
486     $this->assertCount(1, $library['js']);
487     $expected_license = [
488       'name' => 'apache',
489       'url' => 'https://url-to-apache-license',
490       'gpl-compatible' => FALSE,
491     ];
492     $this->assertEquals($library['license'], $expected_license);
493
494     // Copyrighted libraries.
495     $library = $libraries['copyright'];
496     $this->assertCount(1, $library['css']);
497     $this->assertCount(1, $library['js']);
498     $expected_license = [
499       'name' => '© Some company',
500       'gpl-compatible' => FALSE,
501     ];
502     $this->assertEquals($library['license'], $expected_license);
503   }
504
505   /**
506    * Verifies assertions catch invalid CSS declarations.
507    *
508    * @dataProvider providerTestCssAssert
509    */
510
511   /**
512    * Verify an assertion fails if CSS declarations have non-existent categories.
513    *
514    * @param string $extension
515    *   The css extension to build.
516    * @param string $exception_message
517    *   The expected exception message.
518    *
519    * @dataProvider providerTestCssAssert
520    */
521   public function testCssAssert($extension, $exception_message) {
522     $this->moduleHandler->expects($this->atLeastOnce())
523       ->method('moduleExists')
524       ->with($extension)
525       ->will($this->returnValue(TRUE));
526
527     $path = __DIR__ . '/library_test_files';
528     $path = substr($path, strlen($this->root) + 1);
529     $this->libraryDiscoveryParser->setPaths('module', $extension, $path);
530
531     $this->setExpectedException(\AssertionError::class, $exception_message);
532     $this->libraryDiscoveryParser->buildByExtension($extension);
533   }
534
535   /**
536    * Data provider for testing bad CSS declarations.
537    */
538   public function providerTestCssAssert() {
539     return [
540       'css_bad_category' => ['css_bad_category', 'See https://www.drupal.org/node/2274843.'],
541       'Improper CSS nesting' => ['css_bad_nesting', 'CSS must be nested under a category. See https://www.drupal.org/node/2274843.'],
542       'Improper CSS nesting array' => ['css_bad_nesting_array', 'CSS files should be specified as key/value pairs, where the values are configuration options. See https://www.drupal.org/node/2274843.'],
543     ];
544   }
545
546 }
547
548 /**
549  * Wraps the tested class to mock the external dependencies.
550  */
551 class TestLibraryDiscoveryParser extends LibraryDiscoveryParser {
552
553   protected $paths;
554
555   protected $validUris;
556
557   protected function drupalGetPath($type, $name) {
558     return isset($this->paths[$type][$name]) ? $this->paths[$type][$name] : NULL;
559   }
560
561   public function setPaths($type, $name, $path) {
562     $this->paths[$type][$name] = $path;
563   }
564
565   protected function fileValidUri($source) {
566     return isset($this->validUris[$source]) ? $this->validUris[$source] : FALSE;
567   }
568
569   public function setFileValidUri($source, $valid) {
570     $this->validUris[$source] = $valid;
571   }
572
573 }
574
575 if (!defined('CSS_AGGREGATE_DEFAULT')) {
576   define('CSS_AGGREGATE_DEFAULT', 0);
577 }
578 if (!defined('CSS_AGGREGATE_THEME')) {
579   define('CSS_AGGREGATE_THEME', 100);
580 }
581 if (!defined('CSS_BASE')) {
582   define('CSS_BASE', -200);
583 }
584 if (!defined('CSS_LAYOUT')) {
585   define('CSS_LAYOUT', -100);
586 }
587 if (!defined('CSS_COMPONENT')) {
588   define('CSS_COMPONENT', 0);
589 }
590 if (!defined('CSS_STATE')) {
591   define('CSS_STATE', 100);
592 }
593 if (!defined('CSS_THEME')) {
594   define('CSS_THEME', 200);
595 }
596 if (!defined('JS_SETTING')) {
597   define('JS_SETTING', -200);
598 }
599 if (!defined('JS_LIBRARY')) {
600   define('JS_LIBRARY', -100);
601 }
602 if (!defined('JS_DEFAULT')) {
603   define('JS_DEFAULT', 0);
604 }
605 if (!defined('JS_THEME')) {
606   define('JS_THEME', 100);
607 }