Updated Drupal to 8.6. This goes with the following updates because it's possible...
[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    * Tests that the version property of external libraries is handled.
216    *
217    * @covers ::buildByExtension
218    */
219   public function testExternalLibraries() {
220     $this->moduleHandler->expects($this->atLeastOnce())
221       ->method('moduleExists')
222       ->with('external')
223       ->will($this->returnValue(TRUE));
224
225     $path = __DIR__ . '/library_test_files';
226     $path = substr($path, strlen($this->root) + 1);
227     $this->libraryDiscoveryParser->setPaths('module', 'external', $path);
228
229     $libraries = $this->libraryDiscoveryParser->buildByExtension('external');
230     $library = $libraries['example_external'];
231
232     $this->assertEquals('http://example.com/css/example_external.css', $library['css'][0]['data']);
233     $this->assertEquals('http://example.com/example_external.js', $library['js'][0]['data']);
234     $this->assertEquals('3.14', $library['version']);
235   }
236
237   /**
238    * Ensures that CSS weights are taken into account properly.
239    *
240    * @covers ::buildByExtension
241    */
242   public function testDefaultCssWeights() {
243     $this->moduleHandler->expects($this->atLeastOnce())
244       ->method('moduleExists')
245       ->with('css_weights')
246       ->will($this->returnValue(TRUE));
247
248     $path = __DIR__ . '/library_test_files';
249     $path = substr($path, strlen($this->root) + 1);
250     $this->libraryDiscoveryParser->setPaths('module', 'css_weights', $path);
251
252     $libraries = $this->libraryDiscoveryParser->buildByExtension('css_weights');
253     $library = $libraries['example'];
254     $css = $library['css'];
255     $this->assertCount(10, $css);
256
257     // The following default weights are tested:
258     // - CSS_BASE: -200
259     // - CSS_LAYOUT: -100
260     // - CSS_COMPONENT: 0
261     // - CSS_STATE: 100
262     // - CSS_THEME: 200
263     $this->assertEquals(200, $css[0]['weight']);
264     $this->assertEquals(200 + 29, $css[1]['weight']);
265     $this->assertEquals(-200, $css[2]['weight']);
266     $this->assertEquals(-200 + 97, $css[3]['weight']);
267     $this->assertEquals(-100, $css[4]['weight']);
268     $this->assertEquals(-100 + 92, $css[5]['weight']);
269     $this->assertEquals(0, $css[6]['weight']);
270     $this->assertEquals(45, $css[7]['weight']);
271     $this->assertEquals(100, $css[8]['weight']);
272     $this->assertEquals(100 + 8, $css[9]['weight']);
273   }
274
275   /**
276    * Ensures that you cannot provide positive weights for JavaScript libraries.
277    *
278    * @covers ::buildByExtension
279    */
280   public function testJsWithPositiveWeight() {
281     $this->moduleHandler->expects($this->atLeastOnce())
282       ->method('moduleExists')
283       ->with('js_positive_weight')
284       ->will($this->returnValue(TRUE));
285
286     $path = __DIR__ . '/library_test_files';
287     $path = substr($path, strlen($this->root) + 1);
288     $this->libraryDiscoveryParser->setPaths('module', 'js_positive_weight', $path);
289
290     $this->setExpectedException(\UnexpectedValueException::class);
291     $this->libraryDiscoveryParser->buildByExtension('js_positive_weight');
292   }
293
294   /**
295    * Tests a library with CSS/JavaScript and a setting.
296    *
297    * @covers ::buildByExtension
298    */
299   public function testLibraryWithCssJsSetting() {
300     $this->moduleHandler->expects($this->atLeastOnce())
301       ->method('moduleExists')
302       ->with('css_js_settings')
303       ->will($this->returnValue(TRUE));
304
305     $path = __DIR__ . '/library_test_files';
306     $path = substr($path, strlen($this->root) + 1);
307     $this->libraryDiscoveryParser->setPaths('module', 'css_js_settings', $path);
308
309     $libraries = $this->libraryDiscoveryParser->buildByExtension('css_js_settings');
310     $library = $libraries['example'];
311
312     // Ensures that the group and type are set automatically.
313     $this->assertEquals(-100, $library['js'][0]['group']);
314     $this->assertEquals('file', $library['js'][0]['type']);
315     $this->assertEquals($path . '/js/example.js', $library['js'][0]['data']);
316
317     $this->assertEquals(0, $library['css'][0]['group']);
318     $this->assertEquals('file', $library['css'][0]['type']);
319     $this->assertEquals($path . '/css/base.css', $library['css'][0]['data']);
320
321     $this->assertEquals(['key' => 'value'], $library['drupalSettings']);
322   }
323
324   /**
325    * Tests a library with dependencies.
326    *
327    * @covers ::buildByExtension
328    */
329   public function testLibraryWithDependencies() {
330     $this->moduleHandler->expects($this->atLeastOnce())
331       ->method('moduleExists')
332       ->with('dependencies')
333       ->will($this->returnValue(TRUE));
334
335     $path = __DIR__ . '/library_test_files';
336     $path = substr($path, strlen($this->root) + 1);
337     $this->libraryDiscoveryParser->setPaths('module', 'dependencies', $path);
338
339     $libraries = $this->libraryDiscoveryParser->buildByExtension('dependencies');
340     $library = $libraries['example'];
341
342     $this->assertCount(2, $library['dependencies']);
343     $this->assertEquals('external/example_external', $library['dependencies'][0]);
344     $this->assertEquals('example_module/example', $library['dependencies'][1]);
345   }
346
347   /**
348    * Tests a library with a couple of data formats like full URL.
349    *
350    * @covers ::buildByExtension
351    */
352   public function testLibraryWithDataTypes() {
353     $this->moduleHandler->expects($this->atLeastOnce())
354       ->method('moduleExists')
355       ->with('data_types')
356       ->will($this->returnValue(TRUE));
357
358     $path = __DIR__ . '/library_test_files';
359     $path = substr($path, strlen($this->root) + 1);
360     $this->libraryDiscoveryParser->setPaths('module', 'data_types', $path);
361
362     $this->libraryDiscoveryParser->setFileValidUri('public://test.css', TRUE);
363     $this->libraryDiscoveryParser->setFileValidUri('public://test2.css', FALSE);
364
365     $libraries = $this->libraryDiscoveryParser->buildByExtension('data_types');
366     $library = $libraries['example'];
367
368     $this->assertCount(5, $library['css']);
369     $this->assertEquals('external', $library['css'][0]['type']);
370     $this->assertEquals('http://example.com/test.css', $library['css'][0]['data']);
371     $this->assertEquals('file', $library['css'][1]['type']);
372     $this->assertEquals('tmp/test.css', $library['css'][1]['data']);
373     $this->assertEquals('external', $library['css'][2]['type']);
374     $this->assertEquals('//cdn.com/test.css', $library['css'][2]['data']);
375     $this->assertEquals('file', $library['css'][3]['type']);
376     $this->assertEquals('public://test.css', $library['css'][3]['data']);
377   }
378
379   /**
380    * Tests a library with JavaScript-specific flags.
381    *
382    * @covers ::buildByExtension
383    */
384   public function testLibraryWithJavaScript() {
385     $this->moduleHandler->expects($this->atLeastOnce())
386       ->method('moduleExists')
387       ->with('js')
388       ->will($this->returnValue(TRUE));
389
390     $path = __DIR__ . '/library_test_files';
391     $path = substr($path, strlen($this->root) + 1);
392     $this->libraryDiscoveryParser->setPaths('module', 'js', $path);
393
394     $libraries = $this->libraryDiscoveryParser->buildByExtension('js');
395     $library = $libraries['example'];
396
397     $this->assertCount(2, $library['js']);
398     $this->assertEquals(FALSE, $library['js'][0]['minified']);
399     $this->assertEquals(TRUE, $library['js'][1]['minified']);
400   }
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 }