magento2-docker/dev/tests/integration/testsuite/Magento/Test/Integrity/StaticFilesTest.php

341 lines
12 KiB
PHP
Executable File

<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Test\Integrity;
use Magento\Framework\App\Filesystem\DirectoryList;
/**
* An integrity test that searches for references to static files and asserts that they are resolved via fallback
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class StaticFilesTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\Framework\View\Design\FileResolution\Fallback\StaticFile
*/
private $fallback;
/**
* @var \Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple
*/
private $explicitFallback;
/**
* @var \Magento\Framework\View\Design\Theme\FlyweightFactory
*/
private $themeRepo;
/**
* @var \Magento\Framework\View\DesignInterface
*/
private $design;
/**
* @var \Magento\Framework\View\Design\ThemeInterface
*/
private $baseTheme;
/**
* @var \Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Alternative
*/
private $alternativeResolver;
/**
* Factory for simple rule
*
* @var \Magento\Framework\View\Design\Fallback\Rule\SimpleFactory
*/
private $simpleFactory;
/**
* @var \Magento\Framework\Filesystem
*/
private $filesystem;
protected function setUp(): void
{
$om = \Magento\TestFramework\Helper\Bootstrap::getObjectmanager();
$this->fallback = $om->get(\Magento\Framework\View\Design\FileResolution\Fallback\StaticFile::class);
$this->explicitFallback = $om->get(
\Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple::class
);
$this->themeRepo = $om->get(\Magento\Framework\View\Design\Theme\FlyweightFactory::class);
$this->design = $om->get(\Magento\Framework\View\DesignInterface::class);
$this->baseTheme = $om->get(\Magento\Framework\View\Design\ThemeInterface::class);
$this->alternativeResolver = $om->get(
\Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Alternative::class
);
$this->simpleFactory = $om->get(\Magento\Framework\View\Design\Fallback\Rule\SimpleFactory::class);
$this->filesystem = $om->get(\Magento\Framework\Filesystem::class);
}
/**
* Scan references to files from other static files and assert they are correct
*
* The CSS or LESS files may refer to other resources using `import` or url() notation
* We want to check integrity of all these references
* Note that the references may have syntax specific to the Magento preprocessing subsystem
*
* @param string $area
* @param string $themePath
* @param string $locale
* @param string $module
* @param string $filePath
* @param string $absolutePath
* @dataProvider referencesFromStaticFilesDataProvider
*/
public function testReferencesFromStaticFiles($area, $themePath, $locale, $module, $filePath, $absolutePath)
{
$contents = file_get_contents($absolutePath);
preg_match_all(
\Magento\Framework\View\Url\CssResolver::REGEX_CSS_RELATIVE_URLS,
$contents,
$matches
);
foreach ($matches[1] as $relatedResource) {
if (false !== strpos($relatedResource, '@')) { // unable to parse paths with LESS variables/mixins
continue;
}
list($relatedModule, $relatedPath) =
\Magento\Framework\View\Asset\Repository::extractModule($relatedResource);
if ($relatedModule) {
$fallbackModule = $relatedModule;
} else {
if ('less' == pathinfo($filePath, PATHINFO_EXTENSION)) {
/**
* The LESS library treats the related resources with relative links not in the same way as CSS:
* when another LESS file is included, it is embedded directly into the resulting document, but the
* relative paths of related resources are not adjusted accordingly to the new root file.
* Probably it is a bug of the LESS library.
*/
$this->markTestSkipped("Due to LESS library specifics, the '{$relatedResource}' cannot be tested.");
}
$fallbackModule = $module;
$relatedPath = \Magento\Framework\View\FileSystem::getRelatedPath($filePath, $relatedResource);
}
// the $relatedPath will be suitable for feeding to the fallback system
$staticFile = $this->getStaticFile($area, $themePath, $locale, $relatedPath, $fallbackModule);
if (empty($staticFile) && substr($relatedPath, 0, 2) === '..') {
//check if static file exists on lib level
$path = substr($relatedPath, 2);
$libDir = rtrim($this->filesystem->getDirectoryRead(DirectoryList::LIB_WEB)->getAbsolutePath(), '/');
$rule = $this->simpleFactory->create(['pattern' => $libDir]);
$params = ['area' => $area, 'theme' => $themePath, 'locale' => $locale];
$staticFile = $this->alternativeResolver->resolveFile($rule, $path, $params);
}
$this->assertNotEmpty(
$staticFile,
"The related resource cannot be resolved through fallback: '{$relatedResource}'"
);
}
}
/**
* Get a default theme path for specified area
*
* @param string $area
* @return string
* @throws \LogicException
*/
private function getDefaultThemePath($area)
{
switch ($area) {
case 'frontend':
return $this->design->getConfigurationDesignTheme($area);
case 'adminhtml':
return $this->design->getConfigurationDesignTheme($area);
case 'doc':
return 'Magento/blank';
default:
throw new \LogicException('Unable to determine theme path');
}
}
/**
* Get static file through fallback system using specified params
*
* @param string $area
* @param string|\Magento\Framework\View\Design\ThemeInterface $theme - either theme path (string) or theme object
* @param string $locale
* @param string $filePath
* @param string $module
* @param bool $isExplicit
* @return bool|string
*/
private function getStaticFile($area, $theme, $locale, $filePath, $module = null, $isExplicit = false)
{
if ($area == 'base') {
$theme = $this->baseTheme;
}
if (!is_object($theme)) {
$themePath = $theme ?: $this->getDefaultThemePath($area);
$theme = $this->themeRepo->create($themePath, $area);
}
if ($isExplicit) {
$type = \Magento\Framework\View\Design\Fallback\RulePool::TYPE_STATIC_FILE;
return $this->explicitFallback->resolve($type, $filePath, $area, $theme, $locale, $module);
}
return $this->fallback->getFile($area, $theme, $locale, $filePath, $module);
}
/**
* @return array
*/
public function referencesFromStaticFilesDataProvider()
{
return \Magento\Framework\App\Utility\Files::init()->getStaticPreProcessingFiles('*.{less,css}');
}
/**
* There must be either .css or .less file, because if there are both, then .less will not be found by fallback
*
* @param string $area
* @param string $themePath
* @param string $locale
* @param string $module
* @param string $filePath
* @dataProvider lessNotConfusedWithCssDataProvider
*/
public function testLessNotConfusedWithCss($area, $themePath, $locale, $module, $filePath)
{
if (false !== strpos($filePath, 'widgets.css')) {
$filePath .= '';
}
$fileName = pathinfo($filePath, PATHINFO_FILENAME);
$dirName = dirname($filePath);
if ('.' == $dirName) {
$dirName = '';
} else {
$dirName .= '/';
}
$cssPath = $dirName . $fileName . '.css';
$lessPath = $dirName . $fileName . '.less';
$cssFile = $this->getStaticFile($area, $themePath, $locale, $cssPath, $module, true);
$lessFile = $this->getStaticFile($area, $themePath, $locale, $lessPath, $module, true);
$this->assertFalse(
$cssFile && $lessFile,
"A resource file of only one type must exist. Both found: '$cssFile' and '$lessFile'"
);
}
/**
* @return array
*/
public function lessNotConfusedWithCssDataProvider()
{
return \Magento\Framework\App\Utility\Files::init()->getStaticPreProcessingFiles('*.{less,css}');
}
/**
* Test if references $this->getViewFileUrl() in .phtml-files are correct
*
* @param string $phtmlFile
* @param string $area
* @param string $themePath
* @param string $fileId
* @dataProvider referencesFromPhtmlFilesDataProvider
*/
public function testReferencesFromPhtmlFiles($phtmlFile, $area, $themePath, $fileId)
{
list($module, $filePath) = \Magento\Framework\View\Asset\Repository::extractModule($fileId);
$this->assertNotEmpty(
$this->getStaticFile($area, $themePath, 'en_US', $filePath, $module),
"Unable to locate '{$fileId}' reference from {$phtmlFile}"
);
}
/**
* @return array
*/
public function referencesFromPhtmlFilesDataProvider()
{
$result = [];
foreach (\Magento\Framework\App\Utility\Files::init()->getPhtmlFiles(true, false) as $info) {
list($area, $themePath, , , $file) = $info;
foreach ($this->collectGetViewFileUrl($file) as $fileId) {
$result[] = [$file, $area, $themePath, $fileId];
}
}
return $result;
}
/**
* Find invocations of $block->getViewFileUrl() and extract the first argument value
*
* @param string $file
* @return array
*/
private function collectGetViewFileUrl($file)
{
$result = [];
if (preg_match_all('/\$block->getViewFileUrl\(\'([^\']+?)\'\)/', file_get_contents($file), $matches)) {
foreach ($matches[1] as $fileId) {
$result[] = $fileId;
}
}
return $result;
}
/**
* @param string $layoutFile
* @param string $area
* @param string $themePath
* @param string $fileId
* @dataProvider referencesFromLayoutFilesDataProvider
*/
public function testReferencesFromLayoutFiles($layoutFile, $area, $themePath, $fileId)
{
list($module, $filePath) = \Magento\Framework\View\Asset\Repository::extractModule($fileId);
$this->assertNotEmpty(
$this->getStaticFile($area, $themePath, 'en_US', $filePath, $module),
"Unable to locate '{$fileId}' reference from layout XML in {$layoutFile}"
);
}
/**
* @return array
*/
public function referencesFromLayoutFilesDataProvider()
{
$result = [];
$files = \Magento\Framework\App\Utility\Files::init()->getLayoutFiles(['with_metainfo' => true], false);
foreach ($files as $metaInfo) {
list($area, $themePath, , , $file) = array_pad($metaInfo, 5, null);
if (!is_string($file)) {
$this->addWarning(
'Wrong layout file configuration provided. The `file` meta info must be the type of string'
);
continue;
}
foreach ($this->collectFileIdsFromLayout($file) as $fileId) {
$result[] = [$file, $area, $themePath, $fileId];
}
}
return $result;
}
/**
* Collect view file declarations in layout XML-files
*
* @param string $file
* @return array
*/
private function collectFileIdsFromLayout($file)
{
$xml = simplexml_load_file($file);
$elements = $xml->xpath('//head/css|link|script');
$result = [];
if ($elements) {
foreach ($elements as $node) {
$result[] = (string)$node;
}
}
return $result;
}
}