diff --git a/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php b/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php index 3e2cb56..3b39501 100644 --- a/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php +++ b/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php @@ -422,6 +422,17 @@ public function getIterator() { } /** + * Indicates whether this collection contains any assets. + * + * @return bool + * TRUE if contained assets are present, FALSE otherwise. + */ + public function isEmpty() { + return $this->assetStorage->count() === 0; + } + + + /** * Ensures that the asset is of the correct subtype (e.g., css vs. js). * * @param AssetInterface $asset diff --git a/core/lib/Drupal/Core/Asset/AssetGraph.php b/core/lib/Drupal/Core/Asset/AssetGraph.php index 4c17866..f261a0a 100644 --- a/core/lib/Drupal/Core/Asset/AssetGraph.php +++ b/core/lib/Drupal/Core/Asset/AssetGraph.php @@ -109,4 +109,12 @@ protected function processNewVertex(AssetInterface $vertex) { } } } -} \ No newline at end of file + + /** + * {@inheritdoc} + */ + public function transpose() { + // TODO super-important - have to rewrite transpose so that it correctly inverts edge direction + return parent::transpose(); + } +} diff --git a/core/lib/Drupal/Core/Asset/Bag/AssetBag.php b/core/lib/Drupal/Core/Asset/Bag/AssetBag.php index 0943c49..94834b4 100644 --- a/core/lib/Drupal/Core/Asset/Bag/AssetBag.php +++ b/core/lib/Drupal/Core/Asset/Bag/AssetBag.php @@ -8,7 +8,10 @@ namespace Drupal\Core\Asset\Bag; use Drupal\Core\Asset\AssetInterface; +use Drupal\Core\Asset\AssetLibraryRepository; use Drupal\Core\Asset\Bag\AssetBagInterface; +use Drupal\Core\Asset\Collection\CssCollection; +use Drupal\Core\Asset\Collection\JsCollection; use Drupal\Core\Asset\JavascriptAssetInterface; use Drupal\Core\Asset\StylesheetAssetInterface; @@ -25,18 +28,14 @@ class AssetBag implements AssetBagInterface { protected $assets = array(); /** - * Whether this AssetBag contains any JavaScript assets. - * - * @var bool + * @var \Drupal\Core\Asset\Collection\CssCollection */ - protected $hasJs = FALSE; + protected $css; /** - * Whether this AssetBag contains any CSS assets. - * - * @var bool + * @var \Drupal\Core\Asset\Collection\JsCollection */ - protected $hasCss = FALSE; + protected $js; /** * Whether this AssetBag is frozen. @@ -45,6 +44,11 @@ class AssetBag implements AssetBagInterface { */ protected $frozen = FALSE; + public function __construct() { + $this->js = new JsCollection(); + $this->css = new CssCollection(); + } + /** * {@inheritdoc} */ @@ -53,13 +57,14 @@ public function add(AssetInterface $asset) { throw new \LogicException('Assets cannot be added to a frozen AssetBag.', E_ERROR); } - $this->assets[] = $asset; if ($asset instanceof JavascriptAssetInterface) { - $this->hasJs = TRUE; + $this->js->add($asset); } if ($asset instanceof StylesheetAssetInterface) { - $this->hasCss = TRUE; + $this->css->add($asset); } + + return $this; } /** @@ -70,34 +75,32 @@ public function addAssetBag(AssetBagInterface $bag, $freeze = TRUE) { throw new \LogicException('Assets cannot be added to a frozen AssetBag.', E_ERROR); } - foreach ($bag->all() as $asset) { - $this->add($asset); + if ($bag->hasCss()) { + $this->css->mergeCollection($bag->getCss()); + } + if ($bag->hasJs()) { + $this->js->mergeCollection($bag->getJs()); } if ($freeze) { $bag->freeze(); } + + return $this; } /** * {@inheritdoc} */ public function hasCss() { - return $this->hasCss; + return !$this->css->isEmpty(); } /** * {@inheritdoc} */ public function getCss() { - $css = array(); - foreach ($this->assets as $asset) { - if ($asset instanceof StylesheetAssetInterface) { - $css[] = $asset; - } - } - - return $css; + return $this->css; } /** @@ -109,6 +112,8 @@ public function all() { /** * {@inheritdoc} + * + * TODO js settings need a complete overhaul */ public function addJsSetting($data) { $this->javascript['settings']['data'][] = $data; @@ -118,21 +123,14 @@ public function addJsSetting($data) { * {@inheritdoc} */ public function hasJs() { - return $this->hasJs; + return !$this->js->isEmpty(); } /** * {@inheritdoc} */ public function getJs() { - $js = array(); - foreach ($this->assets as $asset) { - if ($asset instanceof JavascriptAssetInterface) { - $js[] = $asset; - } - } - - return $js; + return $this->js; } /** @@ -149,4 +147,21 @@ public function isFrozen() { return $this->frozen; } + /** + * {@inheritdoc} + */ + public function resolveDependencies(AssetLibraryRepository $repository) { + foreach ($this->css as $asset) { + foreach ($repository->resolveDependencies($asset) as $dep) { + $this->add($dep); + } + } + + foreach ($this->js as $asset) { + foreach ($repository->resolveDependencies($asset) as $dep) { + $this->add($dep); + } + } + } + } diff --git a/core/lib/Drupal/Core/Asset/Bag/AssetBagInterface.php b/core/lib/Drupal/Core/Asset/Bag/AssetBagInterface.php index a6d558e..21223ec 100644 --- a/core/lib/Drupal/Core/Asset/Bag/AssetBagInterface.php +++ b/core/lib/Drupal/Core/Asset/Bag/AssetBagInterface.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Asset\Bag; use Drupal\Core\Asset\AssetInterface; +use Drupal\Core\Asset\AssetLibraryRepository; /** * Defines a common interface for asset bags. @@ -29,7 +30,8 @@ * * @param AssetInterface $asset * - * @return mixed + * @return AssetBagInterface + * Returns the current AssetBagInterface object for method chaining. */ public function add(AssetInterface $asset); @@ -38,21 +40,20 @@ public function add(AssetInterface $asset); * * @param AssetBagInterface $bag * - * @return mixed + * @return AssetBagInterface + * Returns the current AssetBagInterface object for method chaining. */ public function addAssetBag(AssetBagInterface $bag); /** * Adds configuration settings for eventual inclusion in drupalSettings. * - * @todo Nice-to-have: create something like JavaScriptSettingAssetInterface. + * TODO refactor & refine to completion * * @param $data * An associative array containing configuration settings, to be eventually * merged into drupalSettings. Settings should be be keyed, typically by * by module name, in order to avoid conflicts in the drupalSettings object. - * Calling this twice with the same data does not change the settings sent - * to the browser: this is idempotent, thanks to drupal_merge_js_settings(). * * @return AssetBagInterface $this * Returns the current AssetBagInterface object for method chaining. @@ -69,7 +70,7 @@ public function hasCss(); /** * Returns the CSS assets in this bag, in the order they were added. * - * @return array + * @return \Drupal\Core\Asset\Collection\AssetCollectionInterface */ public function getCss(); @@ -83,18 +84,11 @@ public function hasJs(); /** * Returns the JavaScript assets in this bag, in the order they were added. * - * @return array + * @return \Drupal\Core\Asset\Collection\AssetCollectionInterface */ public function getJs(); /** - * Returns all assets contained in this object, in the order they were added. - * - * @return array - */ - public function all(); - - /** * Marks this bag as incapable of receiving new data. * * @return void @@ -107,4 +101,14 @@ public function freeze(); * @return bool */ public function isFrozen(); + + /** + * Resolves all contained asset dependencies and add them into this bag. + * + * @param AssetLibraryRepository $repository + * The AssetLibraryRepository against which to resolve asset dependencies. + * + * @return void + */ + public function resolveDependencies(AssetLibraryRepository $repository); } diff --git a/core/lib/Drupal/Core/Asset/Bag/AssetLibrary.php b/core/lib/Drupal/Core/Asset/Bag/AssetLibrary.php index 35f4d2a..14bac54 100644 --- a/core/lib/Drupal/Core/Asset/Bag/AssetLibrary.php +++ b/core/lib/Drupal/Core/Asset/Bag/AssetLibrary.php @@ -40,6 +40,10 @@ class AssetLibrary extends AssetBag implements AssetOrderingInterface { */ protected $dependencies = array(); + protected $predecessors = array(); + + protected $successors = array(); + public function __construct(array $values = array()) { // TODO do it right. $vals = array_intersect_key($values, array_flip(array('title', 'version', 'website', 'dependencies'))); @@ -154,6 +158,48 @@ public function getDependencyInfo() { } /** + * {@inheritdoc} + */ + public function before($asset) { + $this->successors[] = $asset; + } + + /** + * {@inheritdoc} + */ + public function after($asset) { + $this->predecessors[] = $asset; + } + + /** + * {@inheritdoc} + */ + public function getPredecessors() { + return $this->predecessors; + } + + /** + * {@inheritdoc} + */ + public function getSuccessors() { + return $this->successors; + } + + /** + * {@inheritdoc} + */ + public function clearSuccessors() { + $this->successors = array(); + } + + /** + * {@inheritdoc} + */ + public function clearPredecessors() { + $this->predecessors = array(); + } + + /** * Checks if the asset library is frozen, throws an exception if it is. */ protected function attemptWrite() { diff --git a/core/lib/Drupal/Core/Asset/BaseAsset.php b/core/lib/Drupal/Core/Asset/BaseAsset.php index 51cd08a..48a3038 100644 --- a/core/lib/Drupal/Core/Asset/BaseAsset.php +++ b/core/lib/Drupal/Core/Asset/BaseAsset.php @@ -41,9 +41,11 @@ */ protected $metadata; - protected $dependencies; + protected $dependencies = array(); - protected $ordering; + protected $successors = array(); + + protected $predecessors = array(); public function __construct(AssetMetadataBag $metadata, $filters = array(), $sourceRoot = NULL, $sourcePath = NULL) { $this->filters = new FilterCollection($filters); @@ -211,42 +213,41 @@ public function getDependencyInfo() { * {@inheritdoc} */ public function before($asset) { - // TODO: Implement before() method. + $this->successors[] = $asset; } /** * {@inheritdoc} */ public function after($asset) { - // TODO: Implement after() method. + $this->predecessors[] = $asset; } /** * {@inheritdoc} */ public function getPredecessors() { - // TODO: Implement getPredecessors() method. + return $this->predecessors; } /** * {@inheritdoc} */ public function getSuccessors() { - // TODO: Implement getSuccessors() method. + return $this->successors; } /** * {@inheritdoc} */ public function clearSuccessors() { - // TODO: Implement clearSuccessors() method. + $this->successors = array(); } /** * {@inheritdoc} */ public function clearPredecessors() { - // TODO: Implement clearPredecessors() method. + $this->predecessors = array(); } - } diff --git a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php index a669353..f63853c 100644 --- a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php +++ b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php @@ -77,4 +77,12 @@ public function getById($id, $graceful = TRUE); * @return void */ public function reindex(); + + /** + * Indicates whether this collection contains any assets. + * + * @return bool + * TRUE if contained assets are present, FALSE otherwise. + */ + public function isEmpty(); } \ No newline at end of file diff --git a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php index 0ea7939..d7a69e9 100644 --- a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php +++ b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php @@ -6,8 +6,6 @@ */ namespace Drupal\Core\Asset\Collection; -use Assetic\Asset\AssetInterface as AsseticAssetInterface; -use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException; use Drupal\Core\Asset\AssetInterface; /** @@ -62,4 +60,11 @@ public function mergeCollection(AssetCollectionInterface $collection); * @return void */ public function freeze(); + + /** + * Indicates whether or not this collection is frozen. + * + * @return bool + */ + public function isFrozen(); } \ No newline at end of file diff --git a/core/lib/Drupal/Core/Asset/Collection/BaseAssetCollection.php b/core/lib/Drupal/Core/Asset/Collection/BaseAssetCollection.php index e866a3a..ff55f6e 100644 --- a/core/lib/Drupal/Core/Asset/Collection/BaseAssetCollection.php +++ b/core/lib/Drupal/Core/Asset/Collection/BaseAssetCollection.php @@ -16,12 +16,14 @@ * @see CssCollection * @see JsCollection */ -abstract class BaseAssetCollection implements AssetCollectionInterface { +abstract class BaseAssetCollection implements \IteratorAggregate, AssetCollectionInterface { protected $assetStorage; protected $assetIdMap = array(); + protected $frozen = FALSE; + public function __construct() { $this->assetStorage = new \SplObjectStorage(); } @@ -30,6 +32,9 @@ public function __construct() { * {@inheritdoc} */ public function add(AssetInterface $asset) { + $this->attemptWrite(); + $this->ensureCorrectType($asset); + $this->assetStorage->attach($asset); $this->assetIdMap[$asset->id()] = $asset; } @@ -69,28 +74,79 @@ public function reindex() { * {@inheritdoc} */ public function remove($needle, $graceful = TRUE) { - // TODO: Implement remove() method. + $this->attemptWrite(); + + if ((is_string($needle) && $needle = $this->getById($needle, $graceful)) || + $needle instanceof AssetInterface) { + unset($this->assetIdMap[$needle->id()], $this->assetStorage[$needle]); + return TRUE; + } + + return FALSE; } /** * {@inheritdoc} */ public function all() { - // TODO: Implement all() method. + return $this->assetIdMap; } /** * {@inheritdoc} */ public function mergeCollection(AssetCollectionInterface $collection) { - // TODO: Implement mergeCollection() method. + $this->attemptWrite(); + // TODO subtype mismatch checking + + $other_assets = $collection->all(); + + foreach (array_intersect_key($this->assetIdMap, $other_assets) as $id => $asset) { + unset($other_assets[$id]); + } + + foreach ($other_assets as $asset) { + $this->add($asset); + } + + return $this; } /** * {@inheritdoc} */ public function freeze() { - // TODO: Implement freeze() method. + $this->frozen = TRUE; + } + + /** + * {@inheritdoc} + */ + public function isFrozen() { + return $this->frozen; + } + + /** + * {@inheritdoc} + */ + public function getIterator() { + return new \ArrayIterator($this->assetIdMap); + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + return empty($this->assetIdMap); + } + + /** + * Checks if the asset library is frozen, throws an exception if it is. + */ + protected function attemptWrite() { + if ($this->isFrozen()) { + throw new \LogicException('Cannot write to a frozen AssetCollection.'); + } } /** diff --git a/core/lib/Drupal/Core/Asset/CssCollectionGrouperNouveaux.php b/core/lib/Drupal/Core/Asset/CssCollectionGrouperNouveaux.php index 3933954..63d8ef0 100644 --- a/core/lib/Drupal/Core/Asset/CssCollectionGrouperNouveaux.php +++ b/core/lib/Drupal/Core/Asset/CssCollectionGrouperNouveaux.php @@ -6,6 +6,8 @@ */ namespace Drupal\Core\Asset; +use Drupal\Core\Asset\Aggregate\CssAggregateAsset; +use Drupal\Core\Asset\Collection\CssCollection; use Gliph\Traversal\DepthFirst; use Gliph\Visitor\DepthFirstBasicVisitor; use Drupal\Core\Asset\AssetGraph; @@ -13,7 +15,7 @@ /** * Groups CSS assets. */ -class CssCollectionGrouperNouveaux implements AssetCollectionGrouperInterface { +class CssCollectionGrouperNouveaux { /** * @var AssetLibraryRepository @@ -49,19 +51,19 @@ public function __construct(AssetLibraryRepository $repository) { * @return array * A sorted array of asset groups. */ - public function group(array $assets) { + public function group(CssCollection $assets) { $tsl = $this->getOptimalTSL($assets); // TODO replace with CssCollection // TODO ordering suddenly matters here...problem? - $processed = array(); + $processed = new CssCollection(); $last_key = FALSE; foreach ($tsl as $asset) { // TODO fix the visitor - this will fail right now because the optimality data got depleted during traversal $key = $this->optimal_lookup->contains($asset) ? $this->optimal_lookup[$asset] : FALSE; if ($key !== $last_key) { - $processed[] = $aggregate = new CssAggregateAsset(); // TODO implement CSSAggregateAsset + $processed[] = $aggregate = new CssAggregateAsset($asset->getMetadata()); } $aggregate->add($asset); @@ -80,7 +82,7 @@ public function group(array $assets) { * * @throws \LogicException */ - protected function getOptimalTSL(array $assets) { + protected function getOptimalTSL(CssCollection $assets) { // We need to define the optimum minimal group set, given metadata // boundaries across which aggregates cannot be safely made. $this->optimal = array(); @@ -96,10 +98,6 @@ protected function getOptimalTSL(array $assets) { foreach ($assets as $asset) { $graph->addVertex($asset); - foreach ($this->repository->resolveDependencies($asset) as $dependency) { - $graph->addDirectedEdge($asset, $dependency); - } - $k = $this->getGroupKey($asset); if ($k === FALSE) { @@ -119,10 +117,6 @@ protected function getOptimalTSL(array $assets) { // First, transpose the graph in order to get an appropriate answer $transpose = $graph->transpose(); - // Next, check for cycles - if ($cycles = $transpose->getCycles()) { - throw new \LogicException(sprintf('Found %d cycles in CSS asset collection.', count($cycles))); - } // Create a queue of start vertices to prime the traversal. $queue = $this->createSourceQueue($graph, $transpose); diff --git a/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php b/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php index 702597d..92dca24 100644 --- a/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php +++ b/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php @@ -7,6 +7,9 @@ namespace Drupal\Core\Asset\Factory; use Drupal\Core\Asset\AssetInterface; use Drupal\Core\Asset\Bag\AssetBagInterface; +use Drupal\Core\Asset\Metadata\AssetMetadataBag; +use Drupal\Core\Asset\Metadata\CssMetadataBag; +use Drupal\Core\Asset\Metadata\JsMetadataBag; /** * A class that helps to create and collect assets. @@ -44,32 +47,9 @@ class AssetCollector { */ protected $lockKey; - protected $defaultAssetDefaults = array( - 'css' => array( - 'group' => CSS_AGGREGATE_DEFAULT, - 'weight' => 0, - 'every_page' => FALSE, - 'media' => 'all', - 'preprocess' => TRUE, - 'browsers' => array( - 'IE' => TRUE, - '!IE' => TRUE, - ), - ), - 'js' => array( - 'group' => JS_DEFAULT, - 'every_page' => FALSE, - 'weight' => 0, - 'scope' => 'header', - 'cache' => TRUE, - 'preprocess' => TRUE, - 'attributes' => array(), - 'version' => NULL, - 'browsers' => array(), - ), - ); + protected $defaultCssMetadata; - protected $assetDefaults = array(); + protected $defaultJsMetadata; protected $classMap = array( 'css' => array( @@ -84,14 +64,22 @@ class AssetCollector { ), ); - public function __construct() { + public function __construct(AssetBagInterface $bag = NULL) { $this->restoreDefaults(); + + if (!is_null($bag)) { + $this->setBag($bag); + } } /** - * Adds an asset to the injected AM + * Adds an asset to the contained AssetBag. * - * @todo Document. + * It is not necessary to call this method on assets that were created via the + * create() method. + * + * @param AssetInterface $asset + * The asset to add to the contained bag. */ public function add(AssetInterface $asset) { if (empty($this->bag)) { @@ -102,19 +90,32 @@ public function add(AssetInterface $asset) { } /** - * Creates an asset and returns it. + * Creates an asset, stores it in the collector's bag, and returns it. + * + * TODO flesh out these docs to be equivalent to drupal_add_css/js() * * @param string $asset_type - * 'css' or 'js'. + * A string indicating the asset type - 'css' or 'js'. * @param string $source_type - * 'file', 'external' or 'string'. - * @param ??? $data + * A string indicating the source type - 'file', 'external' or 'string'. + * @param string $data + * A string containing data that defines the asset. Appropriate values vary + * depending on the source_type param: + * - 'file': the relative path to the file, or a stream wrapper URI. + * - 'external': the absolute path to the external asset. + * - 'string': a string containing valid CSS or Javascript to be injected + * directly onto the page. * @param array $options - * ??? + * An array of metadata to explicitly set on the asset. These will override + * metadata defaults that are injected onto the asset at creation time. * @param array $filters - * ??? + * An array of filters to apply to the object + * TODO this should, maybe, be removed entirely * * @return \Drupal\Core\Asset\AssetInterface + * + * @throws \InvalidArgumentException + * Thrown if an invalid asset type or source type is passed. */ public function create($asset_type, $source_type, $data, $options = array(), $filters = array()) { if (!isset($this->classMap[$asset_type])) { @@ -124,9 +125,11 @@ public function create($asset_type, $source_type, $data, $options = array(), $fi throw new \InvalidArgumentException(sprintf('Only sources of type "file", "string", or "external" are allowed, "%s" requested.', $source_type)); } + $metadata = $this->getMetadataDefaults($asset_type); + $metadata->replace($options); + $class = $this->classMap[$asset_type][$source_type]; - $asset = new $class($data, $options, $filters); - $asset->setDefaults($this->getDefaults($asset_type)); + $asset = new $class($metadata, $data, $filters); if (!empty($this->bag)) { $this->add($asset); @@ -181,30 +184,63 @@ public function isLocked() { return $this->locked; } - public function setDefaults($type, array $defaults) { - // TODO refactor to use AssetMetadataBag system + public function setDefaultMetadata($type, AssetMetadataBag $metadata) { if ($this->isLocked()) { throw new \Exception('The collector instance is locked. Asset defaults cannot be modified on a locked collector.'); } - $this->assetDefaults[$type] = array_merge($this->assetDefaults[$type], $defaults); - } - public function getDefaults($type = NULL) { - if (!isset($type)) { - return $this->assetDefaults; + if ($type === 'css') { + $this->defaultCssMetadata = $metadata; } - - if (!isset($this->assetDefaults[$type])) { - throw new \InvalidArgumentException(sprintf('The type provided, "%s", is not known.', $type)); + elseif ($type === 'js') { + $this->defaultJsMetadata = $metadata; + } + else { + throw new \InvalidArgumentException(sprintf('Only assets of type "js" or "css" are supported, "%s" requested.', $type)); } + } - return $this->assetDefaults[$type]; + /** + * Gets a clone of the metadata bag for a given asset type. + * + * Clones are returned in order to ensure there is a unique metadata object + * for every asset, and that the default metadata contained in the collector + * cannot be modified externally. + * + * @param $type + * A string, 'css' or 'js', indicating the type of metadata to retrieve. + * + * @return AssetMetadataBag + * + * @throws \InvalidArgumentException + * Thrown if a type other than 'css' or 'js' is provided. + */ + public function getMetadataDefaults($type) { + if ($type === 'css') { + return clone $this->defaultCssMetadata; + } + elseif ($type === 'js') { + return clone $this->defaultJsMetadata; + } + else { + throw new \InvalidArgumentException(sprintf('Only assets of type "js" or "css" are supported, "%s" requested.', $type)); + } } + /** + * Restores metadata default bags to their default state. + * + * This simply creates new instances of CssMetadataBag and JsMetadataBag, as + * those classes have the normal defaults as hardmapped properties. + * + * @throws \Exception + * Thrown if the collector is locked when this method is called. + */ public function restoreDefaults() { if ($this->isLocked()) { throw new \Exception('The collector instance is locked. Asset defaults cannot be modified on a locked collector.'); } - $this->assetDefaults = $this->defaultAssetDefaults; + $this->defaultCssMetadata = new CssMetadataBag(); + $this->defaultJsMetadata = new JsMetadataBag(); } } diff --git a/core/lib/Drupal/Core/Asset/Factory/AssetLibraryCollector.php b/core/lib/Drupal/Core/Asset/Factory/AssetLibraryCollector.php index 3d04b24..84df9ca 100644 --- a/core/lib/Drupal/Core/Asset/Factory/AssetLibraryCollector.php +++ b/core/lib/Drupal/Core/Asset/Factory/AssetLibraryCollector.php @@ -10,6 +10,7 @@ use \Drupal\Core\Asset\AssetLibraryRepository; use Drupal\Core\Asset\Factory\AssetCollector; use Drupal\Core\Asset\Bag\AssetLibrary; +use Drupal\Core\Asset\Metadata\JsMetadataBag; class AssetLibraryCollector { @@ -40,7 +41,7 @@ public function buildLibrary($name, $values) { $collector = new AssetCollector(); $collector->setBag($library); - $collector->setDefaults('js', array('group' => JS_LIBRARY)); + $collector->setDefaultMetadata('js', new JsMetadataBag(array('group' => JS_LIBRARY))); $collector->lock($this->getPrivateKey()); // TODO is locking here a bad idea? return $collector; diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetAssemblyTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetAssemblyTest.php index b80689e..d48cc7c 100644 --- a/core/tests/Drupal/Tests/Core/Asset/AssetAssemblyTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/AssetAssemblyTest.php @@ -11,13 +11,18 @@ use Drupal\Core\Asset\Bag\AssetLibrary; use Drupal\Core\Asset\AssetLibraryRepository; use Drupal\Core\Asset\AssetLibraryReference; +use Drupal\Core\Asset\Collection\CssCollection; +use Drupal\Core\Asset\Collection\JsCollection; use Drupal\Core\Asset\JavascriptFileAsset; use Drupal\Core\Asset\JavascriptStringAsset; use Drupal\Core\Asset\JavascriptExternalAsset; +use Drupal\Core\Asset\Metadata\CssMetadataBag; +use Drupal\Core\Asset\Metadata\JsMetadataBag; use Drupal\Core\Asset\StylesheetFileAsset; use Drupal\Core\Asset\StylesheetStringAsset; use Drupal\Core\Asset\StylesheetExternalAsset; +use Drupal\Core\Extension\ModuleHandler; use Drupal\Tests\UnitTestCase; /** @@ -31,14 +36,14 @@ class AssetAssemblyTest extends UnitTestCase { public static function getInfo() { return array( - 'name' => 'Asset Assembly tests', + 'name' => 'Asset assembly tests', 'description' => 'Tests to ensure assets declared via the various possible approaches come out with the correct properties, in the proper order.', 'group' => 'Asset', ); } public function createJQueryAssetLibrary() { - $library = new AssetLibrary(array(new JavascriptFileAsset('core/misc/jquery.js'))); + $library = new AssetLibrary(array(new JavascriptFileAsset(new JsMetadataBag(), 'core/misc/jquery.js'))); return $library->setTitle('jQuery') ->setVersion('1.8.2') ->setWebsite('http://jquery.com'); @@ -55,8 +60,8 @@ public function testSingleBagAssetAssemblies() { // Dead-simple bag - contains just one css and one js assets, both local files. $bag = new AssetBag(); - $css1 = new StylesheetFileAsset(DRUPAL_ROOT . '/core/misc/vertical-tabs.css'); - $js1 = new JavascriptFileAsset(DRUPAL_ROOT . '/core/misc/ajax.js'); + $css1 = new StylesheetFileAsset(new CssMetadataBag(), DRUPAL_ROOT . '/core/misc/vertical-tabs.css'); + $js1 = new JavascriptFileAsset(new JsMetadataBag(), DRUPAL_ROOT . '/core/misc/ajax.js'); $bag->add($css1); $bag->add($js1); @@ -64,31 +69,19 @@ public function testSingleBagAssetAssemblies() { $this->assertTrue($bag->hasCss(), 'AssetBag correctly reports that it contains CSS assets.'); $this->assertTrue($bag->hasJs(), 'AssetBag correctly reports that it contains javascript assets.'); - $this->assertEquals(array($css1), $bag->getCss()); - $this->assertEquals(array($js1), $bag->getJs()); + $css_collection = new CssCollection(); + $css_collection->add($css1); - $css2 = new StylesheetFileAsset(DRUPAL_ROOT . 'core/misc/dropbutton/dropbutton.base.css'); - $bag->add($css2); - - $this->assertEquals(array($css1, $css2), $bag->getCss()); - - $this->assertEquals(array($css1, $js1, $css2), $bag->all()); - } - - public function testSortingAndDependencyResolution() { - $bag = new AssetBag(); - - $alm = new AssetLibraryRepository(); - $alm->add('system', 'jquery', $this->createJQueryAssetLibrary()); - $dep = new AssetLibraryReference('jquery', $alm); + $js_collection = new JsCollection(); + $js_collection->add($js1); - $css1 = new StylesheetFileAsset(DRUPAL_ROOT . '/core/misc/vertical-tabs.css'); - $js1 = new JavascriptFileAsset(DRUPAL_ROOT . '/core/misc/ajax.js'); - // $js1->addDependency($dep); + $this->assertEquals($css_collection, $bag->getCss()); + $this->assertEquals($js_collection, $bag->getJs()); - $bag->add($css1); - $bag->add($js1); + $css2 = new StylesheetFileAsset(new CssMetadataBag(), DRUPAL_ROOT . 'core/misc/dropbutton/dropbutton.base.css'); + $bag->add($css2); + $css_collection->add($css2); - $this->assertEquals(array(new JavascriptFileAsset('core/misc/jquery.js'), $js1), $bag->getJs()); + $this->assertEquals($css_collection, $bag->getCss()); } } diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetBagTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetBagTest.php index 4246e92..5d5ea1e 100644 --- a/core/tests/Drupal/Tests/Core/Asset/AssetBagTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/AssetBagTest.php @@ -7,28 +7,56 @@ namespace Drupal\Tests\Core\Asset; +use Drupal\Core\Asset\Bag\AssetBag; +use Drupal\Core\Asset\Collection\CssCollection; +use Drupal\Core\Asset\Collection\JsCollection; +use Drupal\Core\Asset\JavascriptFileAsset; +use Drupal\Core\Asset\Metadata\CssMetadataBag; +use Drupal\Core\Asset\Metadata\JsMetadataBag; +use Drupal\Core\Asset\StylesheetFileAsset; use Drupal\Tests\UnitTestCase; /** - * TODO all of it * @group Asset */ class AssetBagTest extends UnitTestCase { public static function getInfo() { return array( - 'name' => '', // TODO give me a name - 'description' => '', // TODO give me a description - 'group' => '', // TODO give me the same group as above + 'name' => 'Asset bag unit tests', + 'description' => 'Unit tests on AssetBag', + 'group' => 'Asset', ); } - public function setUp() { - parent::setUp(); - } + public function testAddValidAsset() { + // Dead-simple bag - contains just one css and one js assets, both local files. + $bag = new AssetBag(); + + $css1 = $this->getMock('Drupal\\Core\\Asset\\StylesheetFileAsset', array(), array(), '', FALSE); + $js1 = $this->getMock('Drupal\\Core\\Asset\\JavascriptFileAsset', array(), array(), '', FALSE); + + $bag->add($css1); + $bag->add($js1); + + $this->assertTrue($bag->hasCss(), 'AssetBag correctly reports that it contains CSS assets.'); + $this->assertTrue($bag->hasJs(), 'AssetBag correctly reports that it contains javascript assets.'); + + $css_collection = new CssCollection(); + $css_collection->add($css1); + + $js_collection = new JsCollection(); + $js_collection->add($js1); + + $this->assertEquals($css_collection, $bag->getCss()); + $this->assertEquals($js_collection, $bag->getJs()); + + $css2 = $this->getMock('Drupal\\Core\\Asset\\StylesheetFileAsset', array(), array(), '', FALSE); + + $bag->add($css2); + $css_collection->add($css2); - public function testStub() { - // TODO anything. without this, phpunit blows up. + $this->assertEquals($css_collection, $bag->getCss()); } } diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetCollectorTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetCollectorTest.php index 1f83aac..20d4535 100644 --- a/core/tests/Drupal/Tests/Core/Asset/AssetCollectorTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/AssetCollectorTest.php @@ -20,12 +20,12 @@ use Drupal\Core\Asset\Bag\AssetBag; use Drupal\Core\Asset\Factory\AssetCollector; +use Drupal\Core\Asset\Metadata\CssMetadataBag; +use Drupal\Core\Asset\Metadata\JsMetadataBag; use Drupal\Tests\UnitTestCase; /** - * Tests for the asset collector. - * - * TODO DOCS, DOCS, DOCS DOCS DOCS + * Unit tests for AssetCollector. * * @group Asset */ @@ -36,32 +36,6 @@ class AssetCollectorTest extends UnitTestCase { */ protected $collector; - protected $builtinDefaults = array( - 'css' => array( - 'group' => CSS_AGGREGATE_DEFAULT, - 'weight' => 0, - 'every_page' => FALSE, - 'media' => 'all', - 'preprocess' => TRUE, - 'browsers' => array( - 'IE' => TRUE, - '!IE' => TRUE, - ), - ), - 'js' => array( - 'group' => JS_DEFAULT, - 'every_page' => FALSE, - 'weight' => 0, - 'scope' => 'header', - 'cache' => TRUE, - 'preprocess' => TRUE, - 'attributes' => array(), - 'version' => NULL, - 'browsers' => array(), - ), - ); - - public static function getInfo() { return array( 'name' => 'Asset Collector tests', @@ -79,11 +53,21 @@ public function setUp() { * Tests that the collector injects provided metadata to created assets. */ public function testMetadataInjection() { - // Test a single value first $asset = $this->collector->create('css', 'file', 'foo', array('group' => CSS_AGGREGATE_THEME)); - $this->assertEquals(CSS_AGGREGATE_THEME, $asset['group'], 'Collector injected user-passed parameters into the created asset.'); + $meta = $asset->getMetadata(); + $this->assertEquals(CSS_AGGREGATE_THEME, $meta->get('group'), 'Collector injected user-passed parameters into the created asset.'); + $this->assertFalse($meta->isDefault('group')); + } + + public function testDefaultPropagation() { + // Test that defaults are correctly applied by the factory. + $meta = new CssMetadataBag(array('every_page' => TRUE, 'group' => CSS_AGGREGATE_THEME)); + $this->collector->setDefaultMetadata('css', $meta); + $css1 = $this->collector->create('css', 'file', 'foo'); - // TODO is it worth testing multiple params? what about weird ones, like weight? + $asset_meta = $css1->getMetadata(); + $this->assertTrue($asset_meta->get('every_page')); + $this->assertEquals(CSS_AGGREGATE_THEME, $asset_meta->get('group')); } /** @@ -101,8 +85,18 @@ public function testAssetsImplicitlyArriveInInjectedBag() { $bag = new AssetBag(); $this->collector->setBag($bag); - $asset2 = $this->collector->create('css', 'file', 'bar'); - $this->assertContains($asset2, $bag->getCss(), 'Created asset was implicitly added to bag.'); + $asset = $this->collector->create('css', 'file', 'bar'); + $this->assertContains($asset, $bag->getCss(), 'Created asset was implicitly added to bag.'); + } + + public function testAddAssetExplicitly() { + $bag = new AssetBag(); + $this->collector->setBag($bag); + + $asset = $this->getMock('Drupal\\Core\\Asset\\StylesheetFileAsset', array(), array(), '', FALSE); + $this->collector->add($asset); + + $this->assertContains($asset, $bag->getCss()); } /** @@ -140,7 +134,7 @@ public function testUnlockFailsWithoutCorrectSecret() { */ public function testLockingPreventsSettingDefaults() { $this->collector->lock($this); - $this->collector->setDefaults('css', array('foo' => 'bar')); + $this->collector->setDefaultMetadata('css', new CssMetadataBag()); } /** @@ -168,36 +162,40 @@ public function testLockingPreventsSettingBag() { } public function testBuiltinDefaultAreTheSame() { - $this->assertEquals($this->builtinDefaults, $this->collector->getDefaults(), 'Expected set of built-in defaults reside in the collector.'); + $this->assertEquals(new CssMetadataBag(), $this->collector->getMetadataDefaults('css')); + $this->assertEquals(new JsMetadataBag(), $this->collector->getMetadataDefaults('js')); } public function testChangeAndRestoreDefaults() { - $changed_defaults = array('every_page' => TRUE, 'group' => CSS_AGGREGATE_THEME); - $this->collector->setDefaults('css', $changed_defaults); - $this->assertEquals($changed_defaults + $this->builtinDefaults['css'], $this->collector->getDefaults('css'), 'Expected combination of built-in and injected defaults reside in the collector.'); + $changed_css = new CssMetadataBag(array('foo' => 'bar', 'every_page' => TRUE)); + $this->collector->setDefaultMetadata('css', $changed_css); + + $this->assertEquals($changed_css, $this->collector->getMetadataDefaults('css')); + $this->assertNotSame($changed_css, $this->collector->getMetadataDefaults('css'), 'Metadata is cloned on retrieval from collector.'); $this->collector->restoreDefaults(); - $this->assertEquals($this->builtinDefaults, $this->collector->getDefaults(), 'Built-in defaults were correctly restored.'); + $this->assertEquals(new CssMetadataBag(), $this->collector->getMetadataDefaults('css')); + + // Do another check to ensure that both metadata bags are correctly reset + $changed_js = new JsMetadataBag(array('scope' => 'footer', 'fizzbuzz' => 'llama')); + $this->collector->setDefaultMetadata('css', $changed_css); + $this->collector->setDefaultMetadata('js', $changed_js); + + $this->assertEquals($changed_css, $this->collector->getMetadataDefaults('css')); + $this->assertEquals($changed_js, $this->collector->getMetadataDefaults('js')); + $this->collector->restoreDefaults(); + $this->assertEquals(new CssMetadataBag(), $this->collector->getMetadataDefaults('css')); + $this->assertEquals(new JsMetadataBag(), $this->collector->getMetadataDefaults('js')); } /** * @expectedException InvalidArgumentException */ public function testGetNonexistentDefault() { - $this->collector->getDefaults('foo'); - $this->fail('No exception thrown when an invalid key was requested.'); + $this->collector->getMetadataDefaults('foo'); } - public function testDefaultPropagation() { - // Test that defaults are correctly applied by the factory. - $this->collector->setDefaults('css', array('every_page' => TRUE, 'group' => CSS_AGGREGATE_THEME)); - $css1 = $this->collector->create('css', 'file', 'foo'); - $this->assertTrue($css1['every_page'], 'Correct default propagated for "every_page" property.'); - $this->assertEquals(CSS_AGGREGATE_THEME, $css1['group'], 'Correct default propagated for "group" property.'); - - // TODO bother testing js? it seems logically redundant - } public function testCreateStylesheetFileAsset() { $css_file1 = $this->collector->create('css', 'file', 'foo'); diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetMetadataTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetMetadataTest.php deleted file mode 100644 index 727940d..0000000 --- a/core/tests/Drupal/Tests/Core/Asset/AssetMetadataTest.php +++ /dev/null @@ -1,52 +0,0 @@ - 'Asset Metadata Tests', - 'description' => 'Tests that asset classes handle their metadata and defaults correctly.', - 'group' => 'Asset', - ); - } - - public function testDefaultsOverriddenByExplicitValues() { - // As this logic is implemented on the common parent, BaseAsset, testing - // one type of asset is sufficient. - $asset = new StylesheetFileAsset('foo', array('group' => CSS_AGGREGATE_THEME, 'every_page' => TRUE)); - $defaults = array( - 'group' => CSS_AGGREGATE_DEFAULT, - 'weight' => 0, - 'every_page' => FALSE, - 'media' => 'all', - 'preprocess' => TRUE, - 'browsers' => array( - 'IE' => TRUE, - '!IE' => TRUE, - ), - ); - $asset->setDefaults($defaults); - - foreach ($defaults as $key => $value) { - if (in_array($key, array('group', 'every_page'))) { - $this->assertNotEquals($value, $asset[$key], 'Explicit value correctly overrides default.'); - } - else { - $this->assertEquals($value, $asset[$key], 'Default value comes through when no explicit value is present.'); - } - } - } -} \ No newline at end of file