summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwebchick2012-10-15 23:24:33 (GMT)
committerwebchick2012-10-15 23:24:33 (GMT)
commit161a3532d3c740e02ed1d0dd4f65d4d96515073f (patch)
tree9a152c2976750581af37ca8a5458b7b72e5b6e47
parent920fd45a839de6937682aa6d894aa62fda3ad64e (diff)
Issue #1734642 by attiks, Jelle_S, Gábor Hojtsy: Added Move breakpoint module into core.
-rw-r--r--core/MAINTAINERS.txt3
-rw-r--r--core/modules/breakpoint/breakpoint.info7
-rw-r--r--core/modules/breakpoint/breakpoint.module437
-rw-r--r--core/modules/breakpoint/config/breakpoint.yml2
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/Breakpoint.php285
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/BreakpointGroup.php200
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointException.php13
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointMediaQueryException.php15
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointNameException.php15
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceException.php15
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceTypeException.php15
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointAPITest.php91
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointCRUDTest.php54
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupAPITest.php78
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupCRUDTest.php61
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupTestBase.php62
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php123
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointTestBase.php51
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php130
-rw-r--r--core/modules/breakpoint/tests/breakpoint_theme_test.info6
-rw-r--r--core/modules/breakpoint/tests/breakpoint_theme_test.module13
-rw-r--r--core/modules/breakpoint/tests/config/breakpoint_theme_test.breakpoint_groups.yml6
-rw-r--r--core/modules/breakpoint/tests/themes/breakpoint_test_theme/breakpoint_test_theme.info5
-rw-r--r--core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoint_groups.yml6
-rw-r--r--core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoints.yml4
25 files changed, 1697 insertions, 0 deletions
diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt
index fff63c1..d937411 100644
--- a/core/MAINTAINERS.txt
+++ b/core/MAINTAINERS.txt
@@ -173,6 +173,9 @@ Block module
Book module
- Peter Wolanin 'pwolanin' http://drupal.org/user/49851
+Breakpoint module
+- Peter Droogmans 'attiks' http://drupal.org/user/105002
+
Color module
- ?
diff --git a/core/modules/breakpoint/breakpoint.info b/core/modules/breakpoint/breakpoint.info
new file mode 100644
index 0000000..fee160f
--- /dev/null
+++ b/core/modules/breakpoint/breakpoint.info
@@ -0,0 +1,7 @@
+name = Breakpoint
+description = Manage breakpoints and breakpoint groups for responsive designs.
+package = Core
+version = VERSION
+core = 8.x
+
+dependencies[] = config
diff --git a/core/modules/breakpoint/breakpoint.module b/core/modules/breakpoint/breakpoint.module
new file mode 100644
index 0000000..2b07f22
--- /dev/null
+++ b/core/modules/breakpoint/breakpoint.module
@@ -0,0 +1,437 @@
+<?php
+
+/**
+ * @file
+ * Manage breakpoints and breakpoint groups for responsive designs.
+ */
+
+use Drupal\breakpoint\Breakpoint;
+use Drupal\breakpoint\BreakpointGroup;
+
+/**
+ * Implements hook_help().
+ */
+function breakpoint_help($path, $arg) {
+ switch ($path) {
+ case 'admin/help#breakpoint':
+ $output = '';
+ $output .= '<h3>' . t('About') . '</h3>';
+ $output .= '<p>' . t('The Breakpoints module keeps track of the height, width, and resolution breakpoints where a responsive design needs to change in order to respond to different devices being used to view the site.') . '</p>';
+ $output .= '<h3>' . t('Uses') . '</h3>';
+ $output .= '<dl>';
+ $output .= '<dt>' . t('Defining breakpoints') . '</dt>';
+ $output .= '<dd><p>' . t('The Breakpoint module can be used by themes and other modules to define breakpoints, which separate the height or width of viewports (screens, printers, and other media output types) into steps. For instance, a width breakpoint of 40em creates two steps: one for widths up to 40em and one for widths above 40em. Breakpoints can be used to define when layouts should shift from one form to another, when images should be resized, and other changes that need to respond to changes in viewport height or width.') . '</p>';
+ $output .= '<p>' . t('<a href="http://www.w3.org/TR/css3-mediaqueries/">Media queries</a> are a formal way to encode breakpoints. For instance, a width breakpoint at 40em would be written as the media query "(min-width: 40em)". Breakpoints are really just media queries with some additional meta-data, such as a name and multiplier information.') . '</p></dd>';
+ $output .= '<dt>' . t('Assigning resolution multipliers to breakpoints') . '</dt>';
+ $output .= '<dd>' . t('Multipliers are a measure of the viewport\'s device resolution, defined to be the ratio between the physical pixel size of the active device and the <a href="http://en.wikipedia.org/wiki/Device_independent_pixel">device-independent pixel</a> size. The Breakpoint module defines multipliers of 1, 1.5, and 2; when defining breakpoints, modules and themes can define which multipliers apply to each breakpoint.') . '</dd>';
+ $output .= '<dt>' . t('Defining breakpoint groups') . '</dt>';
+ $output .= '<dd>' . t('Breakpoints can be organized into groups. Modules and themes should use groups to separate out breakpoints that are meant to be used for different purposes, such as breakpoints for layouts or breakpoints for image sizing.') . '</dd>';
+ $output .= '</dl>';
+
+ return $output;
+ }
+}
+
+/**
+ * Implements hook_enable().
+ *
+ * Import breakpoints from all enabled themes.
+ *
+ * @todo: This should be removed if https://drupal.org/node/1813100 is resolved.
+ */
+function breakpoint_enable() {
+ // Import breakpoints from themes.
+ $themes = list_themes();
+ _breakpoint_theme_enabled(array_keys($themes));
+
+ // Import breakpoints from modules.
+ $modules = module_list();
+ _breakpoint_modules_enabled(array_keys($modules));
+}
+
+/**
+ * Implements hook_themes_enabled().
+ *
+ * @param array $theme_list
+ * An array of theme names.
+ *
+ * @see _breakpoint_theme_enabled()
+ *
+ * @todo: This should be removed if https://drupal.org/node/1813100 is resolved.
+ */
+function breakpoint_themes_enabled($theme_list) {
+ _breakpoint_theme_enabled($theme_list);
+}
+
+/**
+ * Implements hook_themes_disabled().
+ *
+ * @param array $theme_list
+ * An array of theme names.
+ *
+ * @see _breakpoint_delete_breakpoints()
+ *
+ * @todo: This should be removed if https://drupal.org/node/1813100 is resolved.
+ */
+function breakpoint_themes_disabled($theme_list) {
+ _breakpoint_delete_breakpoints($theme_list, Breakpoint::SOURCE_TYPE_THEME);
+}
+
+/**
+ * Implements hook_modules_enabled().
+ *
+ * @param array $modules
+ * An array of the modules that were enabled.
+ *
+ * @see _breakpoint_modules_enabled()
+ *
+ * @todo: This should be removed if https://drupal.org/node/1813100 is resolved.
+*/
+function breakpoint_modules_enabled($modules) {
+ _breakpoint_modules_enabled($modules);
+}
+
+/**
+ * Implements hook_modules_uninstalled().
+ *
+ * @param array $modules
+ * An array of the modules that were uninstalled.
+ *
+ * @see _breakpoint_delete_breakpoints()
+ *
+ * @todo: This should be removed if https://drupal.org/node/1813100 is resolved.
+ */
+function breakpoint_modules_uninstalled($modules) {
+ _breakpoint_delete_breakpoints($modules, Breakpoint::SOURCE_TYPE_MODULE);
+}
+
+/**
+ * Import breakpoints from all new enabled themes.
+ *
+ * @param array $theme_list
+ * An array of theme names.
+ */
+function _breakpoint_theme_enabled($theme_list) {
+ $themes = list_themes();
+ foreach ($theme_list as $theme_key) {
+ if ($themes[$theme_key]->status) {
+ $media_queries = breakpoint_get_theme_media_queries($theme_key);
+ _breakpoint_import_media_queries($theme_key, $themes[$theme_key]->info['name'], Breakpoint::SOURCE_TYPE_THEME, $media_queries);
+ // Import custom groups.
+ _breakpoint_import_breakpoint_groups($theme_key, Breakpoint::SOURCE_TYPE_THEME);
+ }
+ }
+}
+
+/**
+ * Import breakpoints from all new enabled modules.
+ *
+ * @param array $modules
+ * An array of the modules that were enabled.
+ */
+function _breakpoint_modules_enabled($modules) {
+ foreach ($modules as $module) {
+ $media_queries = breakpoint_get_module_media_queries($module);
+ _breakpoint_import_media_queries($module, $module, Breakpoint::SOURCE_TYPE_MODULE, $media_queries);
+ // Import custom groups.
+ _breakpoint_import_breakpoint_groups($module, Breakpoint::SOURCE_TYPE_MODULE);
+ }
+}
+
+/**
+ * Import media queries from a theme or module and create a default group.
+ *
+ * @param string $group_name
+ * Machine readable name of the breakpoint group.
+ * @param string $label
+ * Human readable name of the breakpoint group.
+ * @param string $sourceType
+ * Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ * @param array $media_queries
+ * An array of breakpoints in the form $breakpoint['name'] = 'media query'.
+ */
+function _breakpoint_import_media_queries($group_name, $label, $source_type, $media_queries) {
+ if (!empty($media_queries)) {
+ // Create a new breakpoint group if it doesn't exist.
+ $breakpoint_group = _breakpoint_group_create_or_load($group_name, $label, $group_name, $source_type);
+
+ // Load all media queries, create a breakpoint for each one and add them
+ // to this breakpoint group.
+ foreach ($media_queries as $name => $media_query) {
+ $breakpoint_group->addBreakpointFromMediaQuery($name, $media_query);
+ }
+
+ $breakpoint_group->save();
+ }
+}
+
+/**
+ * Import breakpoint groups from theme or module.
+ *
+ * @param string $source
+ * The theme or module name
+ * @param string $sourceType
+ * Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ */
+function _breakpoint_import_breakpoint_groups($source, $source_type) {
+ $breakpoint_groups = config($source . '.breakpoint_groups');
+ if ($breakpoint_groups) {
+ foreach ($breakpoint_groups->get() as $group_name => $data) {
+ // Breakpoints is mandatory, extra check since this is coming from config.
+ if (isset($data['breakpoints']) && !empty($data['breakpoints'])) {
+ // Create a new breakpoint group if it doesn't exist.
+ $breakpoint_group = _breakpoint_group_create_or_load($group_name, isset($data['label']) ? $data['label'] : $group_name, $source, $source_type);
+ // Add the breakpoints.
+ $breakpoint_group->addBreakpoints($data['breakpoints']);
+ $breakpoint_group->save();
+ }
+ else {
+ throw new \Exception('Illegal config file detected.');
+ }
+ }
+ }
+}
+
+/**
+ * Remove breakpoints from all disabled themes or uninstalled modules.
+ *
+ * The source type has to match the original source type, otherwise the group
+ * will not be deleted. All groups created by the theme or module will be
+ * deleted as well.
+ *
+ * @param array $list
+ * A list of modules or themes that are disabled.
+ * @param string $sourceType
+ * Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ */
+function _breakpoint_delete_breakpoints($list, $source_type) {
+ $ids = config_get_storage_names_with_prefix('breakpoint.breakpoint_group.' . $source_type . '.');
+ $entity_info = entity_get_info('breakpoint_group');
+
+ // Remove the breakpoint.breakpoint part of the breakpoint identifier.
+ foreach ($ids as &$id) {
+ $id = drupal_substr($id, drupal_strlen($entity_info['config prefix']) + 1);
+ }
+ $breakpoint_groups = entity_load_multiple('breakpoint_group', $ids);
+
+ foreach ($breakpoint_groups as $breakpoint_group) {
+ if ($breakpoint_group->sourceType == $source_type && in_array($breakpoint_group->source, $list)) {
+ // Delete the automatically created breakpoint group.
+ $breakpoint_group->delete();
+
+ // Get all breakpoints defined by this theme/module.
+ $breakpoint_ids = drupal_container()->get('config.storage')->listAll('breakpoint.breakpoint.' . $source_type . '.' . $breakpoint_group->id() . '.');
+ $entity_info = entity_get_info('breakpoint');
+
+ // Remove the breakpoint.breakpoint part of the breakpoint identifier.
+ foreach ($breakpoint_ids as &$breakpoint_id) {
+ $breakpoint_id = drupal_substr($breakpoint_id, drupal_strlen($entity_info['config prefix']) + 1);
+ }
+ $breakpoints = entity_load_multiple('breakpoint', $breakpoint_ids);
+
+ // Make sure we only delete breakpoints defined by this theme/module.
+ foreach ($breakpoints as $breakpoint) {
+ if ($breakpoint->sourceType == $source_type && $breakpoint->source == $breakpoint_group->name) {
+ $breakpoint->delete();
+ }
+ }
+ }
+ }
+
+ // Delete groups defined by a module/theme even if that module/theme didn't
+ // define any breakpoints.
+ foreach ($ids as $id) {
+ // Delete all breakpoint groups defined by the theme or module.
+ _breakpoint_delete_breakpoint_groups($id, $source_type);
+ }
+}
+
+/**
+ * Remove breakpoint groups from all disabled themes or uninstalled modules.
+ *
+ * @param array $group_id
+ * Machine readable name of the breakpoint group.
+ * @param string $sourceType
+ * Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ */
+function _breakpoint_delete_breakpoint_groups($group_id, $source_type) {
+ $breakpoint_groups = entity_load_multiple('breakpoint_group');
+ foreach ($breakpoint_groups as $breakpoint_group) {
+ if ($breakpoint_group->sourceType == $source_type && $breakpoint_group->source == $group_id) {
+ $breakpoint_group->delete();
+ }
+ }
+}
+
+/**
+ * Get a list of available breakpoints from a specified theme.
+ *
+ * @param string $theme_key
+ * The name of the theme.
+ *
+ * @return array
+ * An array of breakpoints in the form $breakpoint['name'] = 'media query'.
+ */
+function breakpoint_get_theme_media_queries($theme_key) {
+ $themes = list_themes();
+ if (!isset($themes[$theme_key])) {
+ throw new \Exception('Illegal theme_key passed.');
+ }
+
+ $config = config($theme_key . '.breakpoints');
+ if ($config) {
+ return $config->get();
+ }
+ return array();
+}
+
+/**
+ * Get a list of available breakpoints from a specified module.
+ *
+ * @param string $module
+ * The name of the module.
+ *
+ * @return array
+ * An array of breakpoints in the form $breakpoint['name'] = 'media query'.
+ */
+function breakpoint_get_module_media_queries($module) {
+ if (!module_exists($module)) {
+ throw new \Exception('Illegal module name passed.');
+ }
+
+ $config = config($module . '.breakpoints');
+ if ($config) {
+ return $config->get();
+ }
+ return array();
+}
+
+/**
+ * Implements hook_entity_info().
+ */
+function breakpoint_entity_info() {
+ // Breakpoint.
+ $types['breakpoint'] = array(
+ 'label' => 'Breakpoint',
+ 'entity class' => 'Drupal\breakpoint\Breakpoint',
+ 'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController',
+ 'config prefix' => 'breakpoint.breakpoint',
+ 'entity keys' => array(
+ 'id' => 'id',
+ 'label' => 'label',
+ 'uuid' => 'uuid',
+ ),
+ );
+
+ // Breakpoint group.
+ $types['breakpoint_group'] = array(
+ 'label' => 'Breakpoint group',
+ 'entity class' => 'Drupal\breakpoint\BreakpointGroup',
+ 'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController',
+ 'config prefix' => 'breakpoint.breakpoint_group',
+ 'entity keys' => array(
+ 'id' => 'id',
+ 'label' => 'label',
+ 'uuid' => 'uuid',
+ ),
+ );
+
+ return $types;
+}
+
+/**
+ * Load one breakpoint group by its identifier.
+ *
+ * @param string $id
+ * The id of the breakpoint group to load.
+ *
+ * @return Drupal\breakpoint\BreakpointGroup|false
+ * The breakpoint group, or FALSE if there is no entity with the given id.
+ *
+ * @todo Remove this in a follow-up issue.
+ * @see http://drupal.org/node/1798214
+ */
+function breakpoint_group_load($id) {
+ return entity_load('breakpoint_group', $id);
+}
+
+/**
+ * Load one breakpoint by its identifier.
+ *
+ * @param int $id
+ * The id of the breakpoint to load.
+ *
+ * @return Drupal\breakpoint\Breakpoint
+ * The entity object, or FALSE if there is no entity with the given id.
+ *
+ * @todo Remove this in a follow-up issue.
+ * @see http://drupal.org/node/1798214
+ */
+function breakpoint_load($id) {
+ return entity_load('breakpoint', $id);
+}
+
+/**
+ * Load all breakpoint groups as select options.
+ *
+ * @return array
+ * An array containing breakpoint group labels indexed by their ids.
+ */
+function breakpoint_group_select_options() {
+ $options = array();
+ $breakpoint_groups = entity_load_multiple('breakpoint_group');
+ foreach ($breakpoint_groups as $breakpoint_group) {
+ $options[$breakpoint_group->id()] = $breakpoint_group->label();
+ }
+ asort($options);
+ return $options;
+}
+
+/**
+ * Load all breakpoints as select options.
+ *
+ * @return array
+ * An array containing breakpoints indexed by their ids.
+ */
+function breakpoint_select_options() {
+ $options = array();
+ $breakpoints = entity_load_multiple('breakpoint');
+ foreach ($breakpoints as $breakpoint) {
+ $options[$breakpoint->id()] = $breakpoint->label() . ' (' . $breakpoint->source . ' - ' . $breakpoint->sourceType . ') [' . $breakpoint->mediaQuery . ']';
+ }
+ asort($options);
+ return $options;
+}
+
+/**
+ * Helper function to easily create/load a breakpoint group.
+ *
+ * @param string $name
+ * Machine readable name of the breakpoint group.
+ * @param string $label
+ * Human readable name of the breakpoint group.
+ * @param string $source
+ * Machine readable name of the defining theme or module.
+ * @param string $sourceType
+ * Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ *
+ * @return Drupal\breakpoint\BreakpointGroup
+ *
+ * @see _breakpoint_import_media_queries()
+ * @see _breakpoint_import_breakpoint_groups()
+ */
+function _breakpoint_group_create_or_load($name, $label, $source, $source_type) {
+ // Try loading the breakpoint group.
+ $breakpoint_group = entity_load('breakpoint_group', $source_type . '.' . $source . '.' . $name);
+ // Create a new breakpoint group if it doesn't exist.
+ if (!$breakpoint_group) {
+ // Build a new breakpoint group.
+ $breakpoint_group = entity_create('breakpoint_group', array(
+ 'name' => $name,
+ 'label' => $label,
+ 'source' => $source,
+ 'sourceType' => $source_type,
+ ));
+ }
+ return $breakpoint_group;
+}
diff --git a/core/modules/breakpoint/config/breakpoint.yml b/core/modules/breakpoint/config/breakpoint.yml
new file mode 100644
index 0000000..0ba703d
--- /dev/null
+++ b/core/modules/breakpoint/config/breakpoint.yml
@@ -0,0 +1,2 @@
+multipliers: [1x, 1.5x, 2x]
+
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Breakpoint.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Breakpoint.php
new file mode 100644
index 0000000..db9caf2
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Breakpoint.php
@@ -0,0 +1,285 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Breakpoint.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\breakpoint\InvalidBreakpointException;
+use Drupal\breakpoint\InvalidBreakpointNameException;
+use Drupal\breakpoint\InvalidBreakpointSourceException;
+use Drupal\breakpoint\InvalidBreakpointSourceTypeException;
+use Drupal\breakpoint\InvalidBreakpointMediaQueryException;
+
+/**
+ * Defines the Breakpoint entity.
+ */
+class Breakpoint extends ConfigEntityBase {
+
+ /**
+ * Denotes that a breakpoint or breakpoint group is defined by a theme.
+ */
+ const SOURCE_TYPE_THEME = 'theme';
+
+ /**
+ * Denotes that a breakpoint or breakpoint group is defined by a module.
+ */
+ const SOURCE_TYPE_MODULE = 'module';
+
+ /**
+ * Denotes that a breakpoint or breakpoint group is defined by the user.
+ */
+ const SOURCE_TYPE_USER_DEFINED = 'custom';
+
+ /**
+ * The breakpoint ID (config name).
+ *
+ * @var string
+ */
+ public $id;
+
+ /**
+ * The breakpoint UUID.
+ *
+ * @var string
+ */
+ public $uuid;
+
+ /**
+ * The breakpoint name (machine name) as specified by theme or module.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * The breakpoint label.
+ *
+ * @var string
+ */
+ public $label;
+
+ /**
+ * The breakpoint media query.
+ *
+ * @var string
+ */
+ public $mediaQuery = '';
+
+ /**
+ * The breakpoint source.
+ *
+ * @var string
+ */
+ public $source = 'user';
+
+ /**
+ * The breakpoint source type.
+ *
+ * @var string
+ * Allowed values:
+ * Breakpoint::SOURCE_TYPE_THEME
+ * Breakpoint::SOURCE_TYPE_MODULE
+ * Breakpoint::SOURCE_TYPE_USER_DEFINED
+ */
+ public $sourceType = Breakpoint::SOURCE_TYPE_USER_DEFINED;
+
+ /**
+ * The breakpoint weight.
+ *
+ * @var weight
+ */
+ public $weight = 0;
+
+ /**
+ * The breakpoint multipliers.
+ *
+ * @var multipliers
+ */
+ public $multipliers = array();
+
+ /**
+ * Overrides Drupal\config\ConfigEntityBase::save().
+ */
+ public function save() {
+ // Check if everything is valid.
+ if (!$this->isValid()) {
+ throw new InvalidBreakpointException('Invalid data detected.');
+ }
+
+ // Build an id if none is set.
+ // Since a particular name can be used by multiple theme/modules we need
+ // to make a unique id.
+ if (empty($this->id)) {
+ $this->id = $this->sourceType . '.' . $this->source . '.' . $this->name;
+ }
+
+ // Set the label if none is set.
+ if (empty($this->label)) {
+ $this->label = $this->name;
+ }
+
+ // Remove unused multipliers.
+ $this->multipliers = array_filter($this->multipliers);
+
+ // Always add '1x' multiplier, use array_key_exists since the value might
+ // be NULL.
+ if (!array_key_exists('1x', $this->multipliers)) {
+ $this->multipliers = array('1x' => '1x') + $this->multipliers;
+ }
+ return parent::save();
+ }
+
+ /**
+ * Checks if the breakpoint is valid.
+ *
+ * @throws Drupal\breakpoint\InvalidBreakpointSourceTypeException
+ * @throws Drupal\breakpoint\InvalidBreakpointSourceException
+ * @throws Drupal\breakpoint\InvalidBreakpointNameException
+ * @throws Drupal\breakpoint\InvalidBreakpointMediaQueryException
+ *
+ * @see isValidMediaQuery()
+ */
+ public function isValid() {
+ // Check for illegal values in breakpoint source type.
+ if (!in_array($this->sourceType, array(
+ Breakpoint::SOURCE_TYPE_USER_DEFINED,
+ Breakpoint::SOURCE_TYPE_MODULE,
+ Breakpoint::SOURCE_TYPE_THEME)
+ )) {
+ throw new InvalidBreakpointSourceTypeException(format_string('Invalid source type @source_type', array(
+ '@source_type' => $this->sourceType,
+ )));
+ }
+ // Check for illegal characters in breakpoint source.
+ if (preg_match('/[^a-z_]+/', $this->source)) {
+ throw new InvalidBreakpointSourceException(format_string("Invalid value '@source' for breakpoint source property. Breakpoint source property can only contain lowercase letters and underscores.", array('@source' => $this->source)));
+ }
+ // Check for illegal characters in breakpoint names.
+ if (preg_match('/[^0-9a-z_\-]/', $this->name)) {
+ throw new InvalidBreakpointNameException(format_string("Invalid value '@name' for breakpoint name property. Breakpoint name property can only contain lowercase alphanumeric characters, underscores (_), and hyphens (-).", array('@name' => $this->name)));
+ }
+ return $this::isValidMediaQuery($this->mediaQuery);
+ }
+
+ /**
+ * Checks if a mediaQuery is valid.
+ *
+ * @throws Drupal\breakpoint\InvalidBreakpointMediaQueryException
+ *
+ * @return true
+ * Returns true if the media query is valid.
+ *
+ * @see http://www.w3.org/TR/css3-mediaqueries/
+ * @see http://www.w3.org/Style/CSS/Test/MediaQueries/20120229/reports/implement-report.html
+ * @see https://github.com/adobe/webkit/blob/master/Source/WebCore/css/
+ */
+ public static function isValidMediaQuery($media_query) {
+ // Array describing all known media features and the expected value type or
+ // an array containing the allowed values.
+ $media_features = array(
+ 'width' => 'length', 'min-width' => 'length', 'max-width' => 'length',
+ 'height' => 'length', 'min-height' => 'length', 'max-height' => 'length',
+ 'device-width' => 'length', 'min-device-width' => 'length', 'max-device-width' => 'length',
+ 'device-height' => 'length', 'min-device-height' => 'length', 'max-device-height' => 'length',
+ 'orientation' => array('portrait', 'landscape'),
+ 'aspect-ratio' => 'ratio', 'min-aspect-ratio' => 'ratio', 'max-aspect-ratio' => 'ratio',
+ 'device-aspect-ratio' => 'ratio', 'min-device-aspect-ratio' => 'ratio', 'max-device-aspect-ratio' => 'ratio',
+ 'color' => 'integer', 'min-color' => 'integer', 'max-color' => 'integer',
+ 'color-index' => 'integer', 'min-color-index' => 'integer', 'max-color-index' => 'integer',
+ 'monochrome' => 'integer', 'min-monochrome' => 'integer', 'max-monochrome' => 'integer',
+ 'resolution' => 'resolution', 'min-resolution' => 'resolution', 'max-resolution' => 'resolution',
+ 'scan' => array('progressive', 'interlace'),
+ 'grid' => 'integer',
+ );
+ if ($media_query) {
+ // Strip new lines and trim.
+ $media_query = str_replace(array("\r", "\n"), ' ', trim($media_query));
+
+ // Remove comments /* ... */.
+ $media_query = preg_replace('/\/\*[\s\S]*?\*\//', '', $media_query);
+
+ // Check media list.
+ $parts = explode(',', $media_query);
+ foreach ($parts as $part) {
+ // Split on ' and '
+ $query_parts = explode(' and ', trim($part));
+ $media_type_found = FALSE;
+ foreach ($query_parts as $query_part) {
+ $matches = array();
+ // Try to match: '(media_feature: value)' and variants.
+ if (preg_match('/^\(([\w\-]+)(:\s?([\w\-\.]+))?\)/', trim($query_part), $matches)) {
+ // Single expression like '(color)'.
+ if (isset($matches[1]) && !isset($matches[2])) {
+ if (!array_key_exists($matches[1], $media_features)) {
+ throw new InvalidBreakpointMediaQueryException('Invalid media feature detected.');
+ }
+ }
+ // Full expression like '(min-width: 20em)'.
+ elseif (isset($matches[3]) && !isset($matches[4])) {
+ $value = trim($matches[3]);
+ if (!array_key_exists($matches[1], $media_features)) {
+ // We need to allow vendor prefixed media fetures and make sure we
+ // are future proof, so only check allowed characters.
+ if (!preg_match('/^[a-zA-Z0-9\:\-\\ ]+$/i', trim($matches[1]))) {
+ throw new InvalidBreakpointMediaQueryException('Invalid media query detected.');
+ }
+ }
+ elseif (is_array($media_features[$matches[1]])) {
+ // Check if value is allowed.
+ if (!array_key_exists($value, $media_features[$matches[1]])) {
+ throw new InvalidBreakpointMediaQueryException('Value is not allowed.');
+ }
+ }
+ elseif (isset ($media_features[$matches[1]])) {
+ switch ($media_features[$matches[1]]) {
+ case 'length':
+ $length_matches = array();
+ // Check for a valid number and an allowed unit.
+ if (preg_match('/^(\-)?(\d+(?:\.\d+)?)?((?:|em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|dpi|dpcm))$/i', trim($value), $length_matches)) {
+ // Only -0 is allowed.
+ if ($length_matches[1] === '-' && $length_matches[2] !== '0') {
+ throw new InvalidBreakpointMediaQueryException('Invalid length detected.');
+ }
+ // If there's a unit, a number is needed as well.
+ if ($length_matches[2] === '' && $length_matches[3] !== '') {
+ throw new InvalidBreakpointMediaQueryException('Unit found, value is missing.');
+ }
+ }
+ else {
+ throw new InvalidBreakpointMediaQueryException('Invalid unit detected.');
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Check for screen, only screen, not screen and variants.
+ elseif (preg_match('/^((?:only|not)?\s?)([\w\-]+)$/i', trim($query_part), $matches)) {
+ if ($media_type_found) {
+ throw new InvalidBreakpointMediaQueryException('Only one media type is allowed.');
+ }
+ $media_type_found = TRUE;
+ }
+ // Check for (scan), (only scan), (not scan) and variants.
+ elseif (preg_match('/^((?:only|not)\s?)\(([\w\-]+)\)$/i', trim($query_part), $matches)) {
+ throw new InvalidBreakpointMediaQueryException('Invalid media query detected.');
+ }
+ else {
+ // We need to allow vendor prefixed media fetures and make sure we
+ // are future proof, so only check allowed characters.
+ if (!preg_match('/^[a-zA-Z0-9\-\\ ]+$/i', trim($query_part), $matches)) {
+ throw new InvalidBreakpointMediaQueryException('Invalid media query detected.');
+ }
+ }
+ }
+ }
+ return TRUE;
+ }
+ throw new InvalidBreakpointMediaQueryException('Media query is empty.');
+ }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/BreakpointGroup.php b/core/modules/breakpoint/lib/Drupal/breakpoint/BreakpointGroup.php
new file mode 100644
index 0000000..b6e6a9f
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/BreakpointGroup.php
@@ -0,0 +1,200 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\BreakpointGroup.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\breakpoint\InvalidBreakpointSourceException;
+use Drupal\breakpoint\InvalidBreakpointSourceTypeException;
+
+/**
+ * Defines the BreakpointGroup entity.
+ */
+class BreakpointGroup extends ConfigEntityBase {
+
+ /**
+ * The breakpoint group ID.
+ *
+ * @var string
+ */
+ public $id;
+
+ /**
+ * The breakpoint group UUID.
+ *
+ * @var string
+ */
+ public $uuid;
+
+ /**
+ * The breakpoint group machine name.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * The breakpoint group label.
+ *
+ * @var string
+ */
+ public $label;
+
+ /**
+ * The breakpoint group breakpoints.
+ *
+ * @var array
+ * Array containing all breakpoints of this group.
+ *
+ * @see Drupal\breakpoints\Breakpoint
+ */
+ public $breakpoints = array();
+
+ /**
+ * The breakpoint group source: theme or module name. Use 'user' for
+ * user-created groups.
+ *
+ * @var string
+ */
+ public $source = 'user';
+
+ /**
+ * The breakpoint group source type.
+ *
+ * @var string
+ * Allowed values:
+ * Breakpoint::SOURCE_TYPE_THEME
+ * Breakpoint::SOURCE_TYPE_MODULE
+ * Breakpoint::SOURCE_TYPE_USER_DEFINED
+ *
+ * @see Drupal\breakpoint\Breakpoint
+ */
+ public $sourceType = Breakpoint::SOURCE_TYPE_USER_DEFINED;
+
+ /**
+ * Overrides Drupal\config\ConfigEntityBase::__construct().
+ */
+ public function __construct(array $values, $entity_type) {
+ parent::__construct($values, $entity_type);
+ $this->loadAllBreakpoints();
+ }
+
+ /**
+ * Overrides Drupal\Core\Entity\Entity::save().
+ */
+ public function save() {
+ // Check if everything is valid.
+ if (!$this->isValid()) {
+ throw new InvalidBreakpointException('Invalid data detected.');
+ }
+ if (empty($this->id)) {
+ $this->id = $this->sourceType . '.' . $this->source . '.' . $this->name;
+ }
+ // Only save the keys, but return the full objects.
+ $this->breakpoints = array_keys($this->breakpoints);
+ parent::save();
+ $this->loadAllBreakpoints();
+ }
+
+ /**
+ * Checks if the breakpoint group is valid.
+ *
+ * @throws Drupal\breakpoint\InvalidBreakpointSourceTypeException
+ * @throws Drupal\breakpoint\InvalidBreakpointSourceException
+ *
+ * @return true
+ * Returns true if the breakpoint group is valid.
+ */
+ public function isValid() {
+ // Check for illegal values in breakpoint group source type.
+ if (!in_array($this->sourceType, array(
+ Breakpoint::SOURCE_TYPE_USER_DEFINED,
+ Breakpoint::SOURCE_TYPE_MODULE,
+ Breakpoint::SOURCE_TYPE_THEME)
+ )) {
+ throw new InvalidBreakpointSourceTypeException(format_string('Invalid source type @source_type', array(
+ '@source_type' => $this->sourceType,
+ )));
+ }
+ // Check for illegal characters in breakpoint group source.
+ if (preg_match('/[^a-z_]+/', $this->source) || empty($this->source)) {
+ throw new InvalidBreakpointSourceException(format_string("Invalid value '@source' for breakpoint group source property. Breakpoint group source property can only contain lowercase letters and underscores.", array('@source' => $this->source)));
+ }
+ // Check for illegal characters in breakpoint group name.
+ if (preg_match('/[^a-z0-9_]+/', $this->name || empty($this->name))) {
+ throw new InvalidBreakpointNameException(format_string("Invalid value '@name' for breakpoint group name property. Breakpoint group name property can only contain lowercase letters, numbers and underscores.", array('@name' => $this->name)));
+ }
+ return TRUE;
+ }
+
+ /**
+ * Adds a breakpoint using a name and a media query.
+ *
+ * @param string $name
+ * The name of the breakpoint.
+ * @param string $media_query
+ * Media query.
+ */
+ public function addBreakpointFromMediaQuery($name, $media_query) {
+ // Use the existing breakpoint if it exists.
+ $breakpoint = entity_load('breakpoint', $this->sourceType . '.' . $this->name . '.' . $name);
+ if (!$breakpoint) {
+ // Build a new breakpoint.
+ $breakpoint = entity_create('breakpoint', array(
+ 'name' => $name,
+ 'label' => $name,
+ 'mediaQuery' => $media_query,
+ 'source' => $this->name,
+ 'sourceType' => $this->sourceType,
+ 'weight' => count($this->breakpoints),
+ ));
+ $breakpoint->save();
+ }
+ $this->breakpoints[$breakpoint->id()] = $breakpoint;
+ }
+
+ /**
+ * Adds one or more breakpoints to this group.
+ *
+ * The breakpoint name is either the machine_name or the id of a breakpoint.
+ *
+ * @param array $breakpoints
+ * Array containing breakpoints keyed by their id.
+ */
+ public function addBreakpoints($breakpoints) {
+ foreach ($breakpoints as $breakpoint_name) {
+ // Check if breakpoint exists, assume $breakpoint_name is a machine name.
+ $breakpoint = entity_load('breakpoint', $this->sourceType . '.' . $this->source . '.' . $breakpoint_name);
+ // If the breakpoint doesn't exist, assume $breakpoint_name is an id.
+ if (!$breakpoint) {
+ $breakpoint = entity_load('breakpoint', $breakpoint_name);
+ }
+ // If the breakpoint doesn't exists, do not add it.
+ if ($breakpoint) {
+ // Add breakpoint to group.
+ $this->breakpoints[$breakpoint->id()] = $breakpoint;
+ }
+ }
+ }
+
+ /**
+ * Loads all breakpoints, remove non-existing ones.
+ *
+ * @return array
+ * Array containing breakpoints keyed by their id.
+ */
+ protected function loadAllBreakpoints() {
+ $breakpoints = $this->breakpoints;
+ $this->breakpoints = array();
+ foreach ($breakpoints as $breakpoint_id) {
+ $breakpoint = breakpoint_load($breakpoint_id);
+ if ($breakpoint) {
+ $this->breakpoints[$breakpoint_id] = $breakpoint;
+ }
+ }
+ }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointException.php b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointException.php
new file mode 100644
index 0000000..6889fa2
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointException.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\InvalidBreakpointException.
+ */
+
+namespace Drupal\breakpoint;
+
+/**
+ * Base exception for breakpoint exception.
+ */
+class InvalidBreakpointException extends \RuntimeException {}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointMediaQueryException.php b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointMediaQueryException.php
new file mode 100644
index 0000000..3999965
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointMediaQueryException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\InvalidBreakpointMediaQueryException.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\breakpoint\InvalidBreakpointException;
+
+/**
+ * Exception thrown if an illegal media query is detected.
+ */
+class InvalidBreakpointMediaQueryException extends InvalidBreakpointException {}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointNameException.php b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointNameException.php
new file mode 100644
index 0000000..1121465
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointNameException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\InvalidBreakpointNameException.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\breakpoint\InvalidBreakpointException;
+
+/**
+ * Exception thrown if an invalid name is detected.
+ */
+class InvalidBreakpointNameException extends InvalidBreakpointException {}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceException.php b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceException.php
new file mode 100644
index 0000000..3ad5556
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\InvalidBreakpointSourceException.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\breakpoint\InvalidBreakpointException;
+
+/**
+ * Exception thrown if an invalid source is detected.
+ */
+class InvalidBreakpointSourceException extends InvalidBreakpointException {}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceTypeException.php b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceTypeException.php
new file mode 100644
index 0000000..d9e2483
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceTypeException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\InvalidBreakpointSourceTypeException.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\breakpoint\InvalidBreakpointException;
+
+/**
+ * Exception thrown if an invalid source_type is detected.
+ */
+class InvalidBreakpointSourceTypeException extends InvalidBreakpointException {}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointAPITest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointAPITest.php
new file mode 100644
index 0000000..24c2ae9
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointAPITest.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointAPITest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\breakpoint\Tests\BreakpointsTestBase;
+use Drupal\breakpoint\Breakpoint;
+use Drupal\breakpoint\InvalidBreakpointNameException;
+use Drupal\breakpoint\InvalidBreakpointSourceException;
+use Drupal\breakpoint\InvalidBreakpointSourceTypeException;
+
+/**
+ * Tests for general breakpoint API functions.
+ */
+class BreakpointAPITest extends BreakpointTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Breakpoint general API functions',
+ 'description' => 'Test general API functions of the breakpoint module.',
+ 'group' => 'Breakpoint',
+ );
+ }
+
+ /**
+ * Test Breakpoint::buildConfigName().
+ */
+ public function testConfigName() {
+ // Try an invalid sourceType.
+ $breakpoint = entity_create('breakpoint', array(
+ 'label' => drupal_strtolower($this->randomName()),
+ 'source' => 'custom_module',
+ 'sourceType' => 'oops',
+ ));
+
+ $exception = FALSE;
+ try {
+ $breakpoint->save();
+ }
+ catch (InvalidBreakpointSourceTypeException $e) {
+ $exception = TRUE;
+ }
+ $this->assertTrue($exception, 'breakpoint_config_name: An exception is thrown when an invalid sourceType is entered.');
+
+ // Try an invalid source.
+ $breakpoint->id = '';
+ $breakpoint->sourceType = Breakpoint::SOURCE_TYPE_USER_DEFINED;
+ $breakpoint->source = 'custom*_module source';
+
+ $exception = FALSE;
+ try {
+ $breakpoint->save();
+ }
+ catch (InvalidBreakpointSourceException $e) {
+ $exception = TRUE;
+ }
+ $this->assertTrue($exception, 'breakpoint_config_name: An exception is thrown when an invalid source is entered.');
+
+ // Try an invalid name (make sure there is at least once capital letter).
+ $breakpoint->id = '';
+ $breakpoint->source = 'custom_module';
+ $breakpoint->name = drupal_ucfirst($this->randomName());
+
+ $exception = FALSE;
+ try {
+ $breakpoint->save();
+ }
+ catch (InvalidBreakpointNameException $e) {
+ $exception = TRUE;
+ }
+ $this->assertTrue($exception, 'breakpoint_config_name: An exception is thrown when an invalid name is entered.');
+
+ // Try a valid breakpoint.
+ $breakpoint->id = '';
+ $breakpoint->name = drupal_strtolower($this->randomName());
+ $breakpoint->mediaQuery = 'all';
+
+ $exception = FALSE;
+ try {
+ $breakpoint->save();
+ }
+ catch (\Exception $e) {
+ $exception = TRUE;
+ }
+ $this->assertFalse($exception, 'breakpoint_config_name: No exception is thrown when a valid breakpoint is passed.');
+ $this->assertEqual($breakpoint->id(), Breakpoint::SOURCE_TYPE_USER_DEFINED . '.custom_module.' . $breakpoint->name, 'breakpoint_config_name: A id is set when a valid breakpoint is passed.');
+ }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointCRUDTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointCRUDTest.php
new file mode 100644
index 0000000..1d5d6e1
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointCRUDTest.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointCRUDTest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\breakpoint\Tests\BreakpointTestBase;
+use Drupal\breakpoint\Breakpoint;
+
+/**
+ * Tests for breakpoint CRUD operations.
+ */
+class BreakpointCRUDTest extends BreakpointTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Breakpoint CRUD operations',
+ 'description' => 'Test creation, loading, updating, deleting of breakpoints.',
+ 'group' => 'Breakpoint',
+ );
+ }
+
+ /**
+ * Test CRUD operations for breakpoints.
+ */
+ public function testBreakpointCRUD() {
+ // Add a breakpoint with minimum data only.
+ $breakpoint = entity_create('breakpoint', array(
+ 'label' => drupal_strtolower($this->randomName()),
+ 'mediaQuery' => '(min-width: 600px)',
+ ));
+ $breakpoint->save();
+
+ $this->verifyBreakpoint($breakpoint);
+
+ // Test breakpoint_load_all
+ $all_breakpoints = entity_load_multiple('breakpoint');
+ $config_name = $breakpoint->id();
+ $this->assertTrue(isset($all_breakpoints[$config_name]), 'breakpoint_load_all: New breakpoint is present when loading all breakpoints.');
+ $this->verifyBreakpoint($breakpoint, $all_breakpoints[$config_name]);
+
+ // Update the breakpoint.
+ $breakpoint->weight = 1;
+ $breakpoint->multipliers['2x'] = '2x';
+ $breakpoint->save();
+ $this->verifyBreakpoint($breakpoint);
+
+ // Delete the breakpoint.
+ $breakpoint->delete();
+ $this->assertFalse(breakpoint_load($config_name), 'breakpoint_load: Loading a deleted breakpoint returns false.', 'Breakpoints API');
+ }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupAPITest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupAPITest.php
new file mode 100644
index 0000000..9fd2ee3
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupAPITest.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointGroupAPITest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\breakpoint\Tests\BreakpointsTestBase;
+use Drupal\breakpoint\BreakpointGroup;
+use Drupal\breakpoint\Breakpoint;
+use Drupal\breakpoint\InvalidBreakpointNameException;
+use Drupal\breakpoint\InvalidBreakpointSourceException;
+use Drupal\breakpoint\InvalidBreakpointSourceTypeException;
+
+/**
+ * Tests for general breakpoint group API functions.
+ */
+class BreakpointGroupAPITest extends BreakpointGroupTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Breakpoint group general API functions',
+ 'description' => 'Test general API functions of the breakpoint module.',
+ 'group' => 'Breakpoint',
+ );
+ }
+
+ /**
+ * Test Breakpoint::buildConfigName().
+ */
+ public function testConfigName() {
+ // Try an invalid sourceType.
+ $label = $this->randomName();
+ $breakpoint_group = entity_create('breakpoint_group', array(
+ 'label' => $label,
+ 'name' => drupal_strtolower($label),
+ 'source' => 'custom_module',
+ 'sourceType' => 'oops',
+ ));
+
+ $exception = FALSE;
+ try {
+ $breakpoint_group->save();
+ }
+ catch (InvalidBreakpointSourceTypeException $e) {
+ $exception = TRUE;
+ }
+ $this->assertTrue($exception, 'An exception is thrown when an invalid sourceType is entered.');
+
+ // Try an invalid source.
+ $breakpoint_group->name = '';
+ $breakpoint_group->sourceType = Breakpoint::SOURCE_TYPE_USER_DEFINED;
+ $breakpoint_group->source = 'custom*_module source';
+
+ $exception = FALSE;
+ try {
+ $breakpoint_group->save();
+ }
+ catch (InvalidBreakpointSourceException $e) {
+ $exception = TRUE;
+ }
+ $this->assertTrue($exception, 'An exception is thrown when an invalid source is entered.');
+
+ // Try a valid breakpoint_group.
+ $breakpoint_group->name = 'test';
+ $breakpoint_group->source = 'custom_module_source';
+
+ $exception = FALSE;
+ try {
+ $breakpoint_group->save();
+ }
+ catch (\Exception $e) {
+ $exception = TRUE;
+ }
+ $this->assertFalse($exception, 'No exception is thrown when a valid data is passed.');
+ }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupCRUDTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupCRUDTest.php
new file mode 100644
index 0000000..72d6c1e
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupCRUDTest.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointGroupCRUDTest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\breakpoint\Tests\BreakpointGroupTestBase;
+use Drupal\breakpoint\BreakpointGroup;
+use Drupal\breakpoint\Breakpoint;
+
+/**
+ * Tests for breakpoint group CRUD operations.
+ */
+class BreakpointGroupCRUDTest extends BreakpointGroupTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Breakpoint group CRUD operations',
+ 'description' => 'Test creation, loading, updating, deleting of breakpoint groups.',
+ 'group' => 'Breakpoint',
+ );
+ }
+
+ /**
+ * Test CRUD operations for breakpoint groups.
+ */
+ public function testBreakpointGroupCRUD() {
+ // Add breakpoints.
+ $breakpoints = array();
+ for ($i = 0; $i <= 3; $i++) {
+ $width = ($i + 1) * 200;
+ $breakpoint = entity_create('breakpoint', array(
+ 'name' => drupal_strtolower($this->randomName()),
+ 'weight' => $i,
+ 'mediaQuery' => "(min-width: {$width}px)",
+ ));
+ $breakpoint->save();
+ $breakpoints[$breakpoint->id()] = $breakpoint;
+ }
+ // Add a breakpoint group with minimum data only.
+ $label = $this->randomName();
+
+ $group = entity_create('breakpoint_group', array(
+ 'label' => $label,
+ 'name' => drupal_strtolower($label),
+ ));
+ $group->save();
+ $this->verifyBreakpointGroup($group);
+
+ // Update the breakpoint group.
+ $group->breakpoints = array_keys($breakpoints);
+ $group->save();
+ $this->verifyBreakpointGroup($group);
+
+ // Delete the breakpoint group.
+ $group->delete();
+ $this->assertFalse(entity_load('breakpoint_group', $group->id()), 'breakpoint_group_load: Loading a deleted breakpoint group returns false.', 'Breakpoints API');
+ }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupTestBase.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupTestBase.php
new file mode 100644
index 0000000..febf71d
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupTestBase.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointGroupTestBase.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\breakpoint\BreakpointGroup;
+
+/**
+ * Base class for Breakpoint group tests.
+ */
+abstract class BreakpointGroupTestBase extends WebTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('breakpoint');
+
+ public function setUp() {
+ parent::setUp();
+ }
+
+ /**
+ * Verify that a breakpoint is properly stored.
+ */
+ public function verifyBreakpointGroup(BreakpointGroup $group, BreakpointGroup $compare_set = NULL) {
+ $properties = array(
+ 'label',
+ 'id',
+ 'name',
+ 'breakpoints',
+ 'sourceType',
+ );
+
+ // Verify breakpoint_group_load().
+ $compare_set = is_null($compare_set) ? entity_load('breakpoint_group', $group->id()) : $compare_set;
+
+ foreach ($properties as $property) {
+ $t_args = array(
+ '%group' => $group->label(),
+ '%property' => $property,
+ );
+ if (is_array($compare_set->{$property})) {
+ $this->assertEqual(array_keys($compare_set->{$property}), array_keys($group->{$property}), format_string('breakpoint_group_load: Proper %property for breakpoint group %group.', $t_args), 'Breakpoint API');
+ }
+ else {
+ $t_args = array(
+ '%group' => $group->label(),
+ '%property' => $property,
+ '%property1' => $compare_set->{$property},
+ '%property2' => $group->{$property},
+ );
+ $this->assertEqual($compare_set->{$property}, $group->{$property}, format_string('breakpoint_group_load: Proper %property: %property1 == %property2 for breakpoint group %group.', $t_args), 'Breakpoint API');
+ }
+ }
+ }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php
new file mode 100644
index 0000000..1696c2b
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointMediaQueryTest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\breakpoint\Breakpoint;
+use Drupal\breakpoint\InvalidBreakpointMediaQueryException;
+
+/**
+ * Tests for media queries in a breakpoint.
+ */
+class BreakpointMediaQueryTest extends UnitTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Breakpoint media query tests',
+ 'description' => 'Test validation of media queries.',
+ 'group' => 'Breakpoint',
+ );
+ }
+
+ /**
+ * Test valid media queries.
+ */
+ public function testValidMediaQueries() {
+ $media_queries = array(
+ // Bartik breakpoints.
+ '(min-width: 0px)',
+ 'all and (min-width: 560px) and (max-width:850px)',
+ 'all and (min-width: 851px)',
+ // Seven breakpoints.
+ '(min-width: 0em)',
+ 'screen and (min-width: 40em)',
+ // Stark breakpoints.
+ '(min-width: 0px)',
+ 'all and (min-width: 480px) and (max-width: 959px)',
+ 'all and (min-width: 960px)',
+ // Other media queries.
+ '(orientation)',
+ 'all and (orientation)',
+ 'not all and (orientation)',
+ 'only all and (orientation)',
+ 'screen and (width)',
+ 'screen and (width: 0)',
+ 'screen and (width: 0px)',
+ 'screen and (width: 0em)',
+ 'screen and (min-width: -0)',
+ 'screen and (max-width: 0)',
+ 'screen and (max-width: 0.3)',
+ 'screen and (min-width)',
+ // Multiline and comments.
+ 'screen and /* this is a comment */ (min-width)',
+ "screen\nand /* this is a comment */ (min-width)",
+ "screen\n\nand /* this is\n a comment */ (min-width)",
+ // Unrecognized features are allowed.
+ 'screen and (-webkit-min-device-pixel-ratio: 7)',
+ 'screen and (min-orientation: landscape)',
+ 'screen and (max-orientation: landscape)',
+ );
+
+ foreach ($media_queries as $media_query) {
+ $this->assertTrue(Breakpoint::isValidMediaQuery($media_query), $media_query . ' is valid.');
+ }
+ }
+
+ /**
+ * Test invalid media queries.
+ */
+ public function testInvalidMediaQueries() {
+ $media_queries = array(
+ '',
+ 'not (orientation)',
+ 'only (orientation)',
+ 'all and not all',
+ 'screen and (width: 0xx)',
+ 'screen and (width: -8xx)',
+ 'screen and (width: -xx)',
+ 'screen and (width: xx)',
+ 'screen and (width: px)',
+ 'screen and (width: -8px)',
+ 'screen and (width: -0.8px)',
+ 'screen and (height: 0xx)',
+ 'screen and (height: -8xx)',
+ 'screen and (height: -xx)',
+ 'screen and (height: xx)',
+ 'screen and (height: px)',
+ 'screen and (height: -8px)',
+ 'screen and (height: -0.8px)',
+ 'screen and (device-width: 0xx)',
+ 'screen and (device-width: -8xx)',
+ 'screen and (device-width: -xx)',
+ 'screen and (device-width: xx)',
+ 'screen and (device-width: px)',
+ 'screen and (device-width: -8px)',
+ 'screen and (device-width: -0.8px)',
+ 'screen and (device-height: 0xx)',
+ 'screen and (device-height: -8xx)',
+ 'screen and (device-height: -xx)',
+ 'screen and (device-height: xx)',
+ 'screen and (device-height: px)',
+ 'screen and (device-height: -8px)',
+ 'screen and (device-height: -0.8px)',
+ 'screen and (min-orientation)',
+ 'screen and (max-orientation)',
+ 'screen and (orientation: bogus)',
+ '(orientation: bogus)',
+ 'screen and (ori"entation: bogus)',
+ );
+
+ foreach ($media_queries as $media_query) {
+ try {
+ $this->assertFalse(Breakpoint::isValidMediaQuery($media_query), $media_query . ' is not valid.');
+ }
+ catch (InvalidBreakpointMediaQueryException $e) {
+ $this->assertTrue(TRUE, format_string('%media_query is not valid.', array('%media_query' => $media_query)));
+ }
+ }
+ }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointTestBase.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointTestBase.php
new file mode 100644
index 0000000..f8f2b35
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointTestBase.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointTestBase.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\breakpoint\Breakpoint;
+
+/**
+ * Base class for Breakpoint tests.
+ */
+abstract class BreakpointTestBase extends WebTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('breakpoint');
+
+ public function setUp() {
+ parent::setUp();
+ }
+
+ /**
+ * Verify that a breakpoint is properly stored.
+ */
+ public function verifyBreakpoint(Breakpoint $breakpoint, Breakpoint $compare_breakpoint = NULL) {
+ $properties = array(
+ 'label',
+ 'mediaQuery',
+ 'source',
+ 'sourceType',
+ 'weight',
+ 'multipliers',
+ );
+
+ // Verify breakpoint_load().
+ $compare_breakpoint = is_null($compare_breakpoint) ? breakpoint_load($breakpoint->id()) : $compare_breakpoint;
+ foreach ($properties as $property) {
+ $t_args = array(
+ '%breakpoint' => $breakpoint->label(),
+ '%property' => $property,
+ );
+ $this->assertEqual($compare_breakpoint->{$property}, $breakpoint->{$property}, format_string('breakpoint_load: Proper %property for breakpoint %breakpoint.', $t_args), 'Breakpoint API');
+ }
+ }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php
new file mode 100644
index 0000000..0737611
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointsThemeTest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\breakpoint\Tests\BreakpointGroupTestBase;
+use Drupal\breakpoint\BreakpointGroup;
+use Drupal\breakpoint\Breakpoint;
+
+/**
+ * Test breakpoints provided by themes.
+ */
+class BreakpointThemeTest extends BreakpointGroupTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('breakpoint_theme_test');
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Breakpoint theme functionality',
+ 'description' => 'Thoroughly test the breakpoints provided by a theme.',
+ 'group' => 'Breakpoint',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp();
+ theme_enable(array('breakpoint_test_theme'));
+ }
+
+ /**
+ * Test the breakpoints provided by a theme.
+ */
+ public function testThemeBreakpoints() {
+ // Verify the breakpoint group for breakpoint_test_theme was created.
+ $breakpoint_group_obj = entity_create('breakpoint_group', array(
+ 'label' => 'Breakpoint test theme',
+ 'name' => 'breakpoint_test_theme',
+ 'source' => 'breakpoint_test_theme',
+ 'sourceType' => Breakpoint::SOURCE_TYPE_THEME,
+ 'id' => Breakpoint::SOURCE_TYPE_THEME . '.breakpoint_test_theme.breakpoint_test_theme',
+ ));
+ $breakpoint_group_obj->breakpoints = array(
+ 'theme.breakpoint_test_theme.mobile' => array(),
+ 'theme.breakpoint_test_theme.narrow' => array(),
+ 'theme.breakpoint_test_theme.wide' => array(),
+ 'theme.breakpoint_test_theme.tv' => array(),
+ );
+
+ // Verify we can load this breakpoint defined by the theme.
+ $this->verifyBreakpointGroup($breakpoint_group_obj);
+
+ // Disable the test theme and verify the breakpoint group is deleted.
+ theme_disable(array('breakpoint_test_theme'));
+ $this->assertFalse(entity_load('breakpoint_group', $breakpoint_group_obj->id()), 'breakpoint_group_load: Loading a deleted breakpoint group returns false.', 'Breakpoint API');
+ }
+
+ /**
+ * Test the breakpoints defined by the custom group.
+ */
+ public function testThemeBreakpointGroup() {
+ // Verify the breakpoint group 'test' was created by breakpoint_test_theme.
+ $breakpoint_group_obj = entity_create('breakpoint_group', array(
+ 'label' => 'Test Theme',
+ 'name' => 'test',
+ 'sourceType' => Breakpoint::SOURCE_TYPE_THEME,
+ 'source' => 'breakpoint_test_theme',
+ 'id' => Breakpoint::SOURCE_TYPE_THEME . '.breakpoint_test_theme.test',
+ ));
+ $breakpoint_group_obj->breakpoints = array(
+ 'theme.breakpoint_test_theme.mobile' => array('1.5x', '2.x'),
+ 'theme.breakpoint_test_theme.narrow' => array(),
+ 'theme.breakpoint_test_theme.wide' => array(),
+ );
+
+ // Verify we can load this breakpoint defined by the theme.
+ $this->verifyBreakpointGroup($breakpoint_group_obj);
+
+ // Disable the test theme and verify the breakpoint group is deleted.
+ theme_disable(array('breakpoint_test_theme'));
+ $this->assertFalse(entity_load('breakpoint_group', $breakpoint_group_obj->id()), 'breakpoint_group_load: Loading a deleted breakpoint group returns false.', 'Breakpoint API');
+ }
+
+ /**
+ * Test the breakpoints defined by the custom group in the module.
+ */
+ public function testThemeBreakpointGroupModule() {
+ // Call the import manually, since the testbot needs to enable the module
+ // first, otherwise the theme isn't detected.
+ _breakpoint_import_breakpoint_groups('breakpoint_theme_test', Breakpoint::SOURCE_TYPE_MODULE);
+
+ // Verify the breakpoint group 'module_test' was created by
+ // breakpoint_theme_test module.
+ $breakpoint_group_obj = entity_create('breakpoint_group', array(
+ 'label' => 'Test Module',
+ 'name' => 'module_test',
+ 'sourceType' => Breakpoint::SOURCE_TYPE_MODULE,
+ 'source' => 'breakpoint_theme_test',
+ 'id' => Breakpoint::SOURCE_TYPE_MODULE . '.breakpoint_theme_test.module_test',
+ ));
+ $breakpoint_group_obj->breakpoints = array(
+ 'theme.breakpoint_test_theme.mobile' => array(),
+ 'theme.breakpoint_test_theme.narrow' => array(),
+ 'theme.breakpoint_test_theme.wide' => array(),
+ );
+
+ // Verify we can load this breakpoint defined by the theme.
+ $this->verifyBreakpointGroup($breakpoint_group_obj);
+
+ // Disable the test theme and verify the breakpoint group still exists.
+ theme_disable(array('breakpoint_test_theme'));
+ $this->assertTrue(entity_load('breakpoint_group', $breakpoint_group_obj->id()), 'Breakpoint group still exists if theme is disabled.');
+
+ // Disable the test module and verify the breakpoint group still exists.
+ module_disable(array('breakpoint_theme_test'));
+ $this->assertTrue(entity_load('breakpoint_group', $breakpoint_group_obj->id()), 'Breakpoint group still exists if module is disabled.');
+
+ // Uninstall the test module and verify the breakpoint group is deleted.
+ module_uninstall(array('breakpoint_theme_test'));
+ $this->assertFalse(entity_load('breakpoint_group', $breakpoint_group_obj->id()), 'Breakpoint group is removed if module is uninstalled.');
+ }
+
+}
diff --git a/core/modules/breakpoint/tests/breakpoint_theme_test.info b/core/modules/breakpoint/tests/breakpoint_theme_test.info
new file mode 100644
index 0000000..d2896f6
--- /dev/null
+++ b/core/modules/breakpoint/tests/breakpoint_theme_test.info
@@ -0,0 +1,6 @@
+name = Breakpoint theme test
+description = Test breakpoints provided by themes
+package = Other
+core = 8.x
+hidden = TRUE
+dependencies[] = breakpoint
diff --git a/core/modules/breakpoint/tests/breakpoint_theme_test.module b/core/modules/breakpoint/tests/breakpoint_theme_test.module
new file mode 100644
index 0000000..50b5ff0
--- /dev/null
+++ b/core/modules/breakpoint/tests/breakpoint_theme_test.module
@@ -0,0 +1,13 @@
+<?php
+/**
+ * @file
+ * Test breakpoint functionality for breakpoints provided by themes.
+ */
+
+/**
+ * Implements hook_system_theme_info().
+ */
+function breakpoint_theme_test_system_theme_info() {
+ $themes['breakpoint_test_theme'] = drupal_get_path('module', 'breakpoint_theme_test') . '/themes/breakpoint_test_theme/breakpoint_test_theme.info';
+ return $themes;
+}
diff --git a/core/modules/breakpoint/tests/config/breakpoint_theme_test.breakpoint_groups.yml b/core/modules/breakpoint/tests/config/breakpoint_theme_test.breakpoint_groups.yml
new file mode 100644
index 0000000..4e3e6f0
--- /dev/null
+++ b/core/modules/breakpoint/tests/config/breakpoint_theme_test.breakpoint_groups.yml
@@ -0,0 +1,6 @@
+module_test:
+ label: Test Module
+ breakpoints:
+ - theme.breakpoint_test_theme.mobile
+ - theme.breakpoint_test_theme.narrow
+ - theme.breakpoint_test_theme.wide
diff --git a/core/modules/breakpoint/tests/themes/breakpoint_test_theme/breakpoint_test_theme.info b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/breakpoint_test_theme.info
new file mode 100644
index 0000000..e3dab72
--- /dev/null
+++ b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/breakpoint_test_theme.info
@@ -0,0 +1,5 @@
+name = Breakpoint test theme
+description = Test theme for breakpoint.
+core = 8.x
+base theme = bartik
+hidden = TRUE
diff --git a/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoint_groups.yml b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoint_groups.yml
new file mode 100644
index 0000000..bde92d8
--- /dev/null
+++ b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoint_groups.yml
@@ -0,0 +1,6 @@
+test:
+ label: Test Theme
+ breakpoints:
+ - mobile
+ - narrow
+ - wide
diff --git a/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoints.yml b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoints.yml
new file mode 100644
index 0000000..534fbf5
--- /dev/null
+++ b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoints.yml
@@ -0,0 +1,4 @@
+mobile: '(min-width: 0px)'
+narrow: '(min-width: 560px)'
+wide: '(min-width: 851px)'
+tv: 'only screen and (min-width: 3456px)'