diff --git a/core/modules/breakpoints/README.txt b/core/modules/breakpoints/README.txt new file mode 100644 index 0000000..83cd585 --- /dev/null +++ b/core/modules/breakpoints/README.txt @@ -0,0 +1,4 @@ +-- SUMMARY -- + +The Breakpoints module allows you to manage all your breakpoints and organize +them into Breakpointsets. diff --git a/core/modules/breakpoints/breakpoints.info b/core/modules/breakpoints/breakpoints.info new file mode 100644 index 0000000..0e274f2 --- /dev/null +++ b/core/modules/breakpoints/breakpoints.info @@ -0,0 +1,9 @@ +name = Breakpoints +description = Manage breakpoints and breakpoint sets. +package = Core +version = VERSION +core = 8.x +files[] = breakpoints.module +files[] = breakpoints.install + +dependencies[] = config diff --git a/core/modules/breakpoints/breakpoints.install b/core/modules/breakpoints/breakpoints.install new file mode 100644 index 0000000..c125f9b --- /dev/null +++ b/core/modules/breakpoints/breakpoints.install @@ -0,0 +1,5 @@ +status) { + // Check theme_key.breakpoints.yml first + $theme_breakpoints = breakpoints_get_theme_breakpoint_list($theme_key); + if (!empty($theme_breakpoints)) { + $weight = 0; + // Build a breakpoint set for each theme + $breakpointset = new BreakpointSet(); + $breakpointset->id = $theme_key; + $breakpointset->label = $themes[$theme_key]->info['name']; + $breakpointset->sourceType = Breakpoint::BREAKPOINTS_SOURCE_TYPE_THEME; + foreach ($theme_breakpoints as $name => $mediaQuery) { + $breakpoint = new Breakpoint; + $breakpoint->name = $name; + $breakpoint->label = ucfirst($name); + $breakpoint->mediaQuery = $mediaQuery; + $breakpoint->source = $theme_key; + $breakpoint->sourceType = Breakpoint::BREAKPOINTS_SOURCE_TYPE_THEME; + $breakpoint->status = TRUE; + $breakpoint->weight = $weight++; + $breakpoint->save(); + $breakpointset->breakpoints[$breakpoint->id()] = $breakpoint; + } + $breakpointset->save(); + $uri = $breakpointset->uri(); + if ($uri) { + $uri_options = $uri; + unset($uri_options['path']); + $uri = $uri['path']; + } + $message = t('The breakpoints from theme %theme are imported and !setlink.', array( + '%theme' => check_plain($themes[$theme_key]->info['name']), + '!setlink' => module_exists('breakpoints_ui') && $uri ? l(t('a new breakpoint set is created'), $uri, $uri_options) : t('a new breakpoint set is created'), + )); + drupal_set_message($message, 'status'); + } + } + } +} + +/** + * Implements hook_themes_disabled(). + * Remove breakpoints from all disabled themes. + */ +function breakpoints_themes_disabled($theme_list) { + $breakpointsets = entity_load_multiple('breakpoints_breakpointset', $theme_list); + foreach ($breakpointsets as $breakpointset) { + $breakpointset->delete(); + // delete all breakpoints defined by this theme. + $names = drupal_container()->get('config.storage')->listAll('breakpoints.breakpoint.' . BREAKPOINTS_SOURCE_TYPE_THEME . '.' . $breakpointset->id() . '.'); + $entity_info = entity_get_info('breakpoints_breakpoint'); + + foreach ($names as &$name) { + $name = substr($name, strlen($entity_info['config prefix']) + 1); + } + $breakpoints = entity_load_multiple('breakpoints_breakpoint', $names); + + foreach ($breakpoints as $breakpoint) { + $breakpoint->delete(); + } + } +} + +/** + * Load general settings. + */ +function breakpoints_settings() { + $config = config('breakpoints'); + if ($config->isNew()) { + return FALSE; + } + return (object)$config->get(); +} + +/** + * Save general settings. + * + * @param array $multipliers + * array containing multipliers. + */ +function breakpoints_settings_save($multipliers) { + $config = config('breakpoints'); + if ($config->isNew()) { + return FALSE; + } + $config->set('multipliers', $multipliers); + $config->save(); +} + +/** + * Reload breakpoint sets as they were defined in the theme. + * + * @param string $theme_key + * + * @return BreakpointSet + */ +function breakpoints_breakpoints_set_reload_from_theme($theme_key) { + // Clear caches so theme.info is fresh. + system_rebuild_theme_data(); + drupal_theme_rebuild(); + + $themes = list_themes(); + if ($themes[$theme_key]->status) { + $theme_breakpoints = breakpoints_get_theme_breakpoint_list($theme_key); + if (!empty($theme_breakpoints)) { + $weight = 0; + // Build a set for each theme + $breakpointset = new BreakpointSet(); + $breakpointset->id = $theme_key; + $breakpointset->label = $themes[$theme_key]->info['name']; + $breakpointset->sourceType = Breakpoint::BREAKPOINTS_SOURCE_TYPE_THEME; + foreach ($theme_breakpoints as $name => $mediaQuery) { + $breakpoint = new Breakpoint; + $breakpoint->name = $name; + $breakpoint->label = ucfirst($name); + $breakpoint->mediaQuery = $mediaQuery; + $breakpoint->source = $theme_key; + $breakpoint->sourceType = Breakpoint::BREAKPOINTS_SOURCE_TYPE_THEME; + $breakpoint->status = TRUE; + $breakpoint->weight = $weight++; + $breakpoint->save(); + $breakpointset->breakpoints[$breakpoint->getConfigName()] = $breakpoint; + } + } + return $breakpointset; + } + return FALSE; +} + +/** + * Get a list of available breakpoints from a specified theme. + * + * @param $theme_key + * The name of the theme. + * + * @return + * An array of breakpoints in the form $breakpoint['name'] = 'media query'. + */ +function breakpoints_get_theme_breakpoint_list($theme_key) { + $themes = list_themes(); + if (!isset($themes[$theme_key])) { + return array(); + } + + $config = config($theme_key . '.breakpoints'); + if ($config) { + return $config->get(); + } + return array(); +} + +/** + * Implements hook_entity_info(). + */ +function breakpoints_entity_info() { + // Breakpoint + $types['breakpoints_breakpoint'] = array( + 'label' => 'Breakpoint', + 'entity class' => 'Drupal\breakpoints\Breakpoint', + 'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController', + 'config prefix' => 'breakpoints.breakpoint', + 'entity keys' => array( + 'id' => 'id', + 'label' => 'label', + 'uuid' => 'uuid', + ), + ); + + // Breakpointset + $types['breakpoints_breakpointset'] = array( + 'label' => 'Breakpoint Set', + 'entity class' => 'Drupal\breakpoints\BreakpointSet', + 'controller class' => 'Drupal\breakpoints\BreakpointSetController', + 'config prefix' => 'breakpoints.breakpointset', + 'entity keys' => array( + 'id' => 'id', + 'label' => 'label', + 'uuid' => 'uuid', + ), + ); + + return $types; +} + +/** + * Load one breakpoint set by its identifier. + */ +function breakpoints_breakpointset_load($id) { + return entity_load('breakpoints_breakpointset', $id); +} + +/** + * Load all breakpoint sets. + */ +function breakpoints_breakpointset_load_all() { + $breakpointsets = entity_load_multiple('breakpoints_breakpointset'); + return $breakpointsets; +} + +/** + * Load one breakpoint by its identifier. + */ +function breakpoints_breakpoint_load($id) { + return entity_load('breakpoints_breakpoint', $id); +} + +/** + * Load all breakpoints. + */ +function breakpoints_breakpoint_load_all() { + $breakpoints = entity_load_multiple('breakpoints_breakpoint'); + return $breakpoints; +} diff --git a/core/modules/breakpoints/config/breakpoints.yml b/core/modules/breakpoints/config/breakpoints.yml new file mode 100644 index 0000000..0ba703d --- /dev/null +++ b/core/modules/breakpoints/config/breakpoints.yml @@ -0,0 +1,2 @@ +multipliers: [1x, 1.5x, 2x] + diff --git a/core/modules/breakpoints/lib/Drupal/breakpoints/Breakpoint.php b/core/modules/breakpoints/lib/Drupal/breakpoints/Breakpoint.php new file mode 100644 index 0000000..a84f0ba --- /dev/null +++ b/core/modules/breakpoints/lib/Drupal/breakpoints/Breakpoint.php @@ -0,0 +1,281 @@ +id)) { + $this->id = $this->buildConfigName(); + } + if (empty($this->label)) { + $this->label = ucfirst($this->name); + } + if (!$this->isValid()) { + throw new Exception(t('Invalid media query detected.')); + } + return parent::save(); + } + + /** + * Get config name. + */ + public function getConfigName() { + return $this->sourceType + . '.' . $this->source + . '.' . $this->name; + } + + /** + * Build config name. + */ + protected function buildConfigName() { + // Check for illegal values in breakpoint source type. + if (!in_array($this->sourceType, array( + BREAKPOINTS_SOURCE_TYPE_CUSTOM, + BREAKPOINTS_SOURCE_TYPE_MODULE, + BREAKPOINTS_SOURCE_TYPE_THEME) + )) { + throw new Exception( + t( + "Expected one of '@custom', '@module' or '@theme' for breakpoint sourceType property but got '@sourcetype'.", + array( + '@custom' => \BREAKPOINTS_SOURCE_TYPE_CUSTOM, + '@module' => \BREAKPOINTS_SOURCE_TYPE_MODULE, + '@theme' => \BREAKPOINTS_SOURCE_TYPE_THEME, + '@sourcetype' => $this->sourceType, + ) + ) + ); + } + // Check for illegal characters in breakpoint source. + if (preg_match('/[^a-z_]+/', $this->source)) { + throw new Exception(t("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 Exception(t("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->sourceType + . '.' . $this->source + . '.' . $this->name; + } + + /** + * Shortcut function to enable a breakpoint and save it. + * @see breakpoints_breakpoint_action_confirm_submit() + */ + public function enable() { + if (!$this->status) { + $this->status = 1; + $this->save(); + } + } + + /** + * Shortcut function to disable a breakpoint and save it. + * @see breakpoints_breakpoint_action_confirm_submit() + */ + public function disable() { + if ($this->status) { + $this->status = 0; + $this->save(); + } + } + + /** + * Check if the mediaQuery is valid. + * @see isValidMediaQuery() + */ + public function isValid() { + return $this::isValidMediaQuery($this->mediaQuery); + } + + /** + * Check if a mediaQuery is valid. + * @see http://www.w3.org/TR/css3-mediaqueries/ + * @see http://www.w3.org/Style/CSS/Test/MediaQueries/20120229/reports/implement-report.html + */ + public static function isValidMediaQuery($media_query) { + $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) { + // @todo: strip comments, new lines, .... + // Check mediaQuery_list: S* [mediaQuery [ ',' S* mediaQuery ]* ]? + $parts = explode(',', trim($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(); + // Check expression: '(' S* media_feature S* [ ':' S* expr ]? ')' S* + if (preg_match('/^\(([\w\-]+)(:\s?([\w\-]+))?\)/', trim($query_part), $matches)) { + // Single expression. + if (isset($matches[1]) && !isset($matches[2])) { + if (!array_key_exists($matches[1], $media_features)) { + return FALSE; + } + } + // Full expression. + elseif (isset($matches[3]) && !isset($matches[4])) { + $value = trim($matches[3]); + if (!array_key_exists($matches[1], $media_features)) { + return FALSE; + } + if (is_array($media_features[$matches[1]])) { + // Check if value is allowed. + if (!array_key_exists($value, $media_features[$matches[1]])) { + return FALSE; + } + } + else { + switch ($media_features[$matches[1]]) { + case 'length': + $length_matches = array(); + if (preg_match('/^(\-)?(\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') { + return FALSE; + } + // If there's a unit, a number is needed as well. + if ($length_matches[2] === '' && $length_matches[3] !== '') { + return FALSE; + } + } + else { + return FALSE; + } + break; + } + } + } + } + // Check [ONLY | NOT]? S* media_type + elseif (preg_match('/((?:only|not)?\s?)([\w\-]+)$/i', trim($query_part), $matches)) { + if ($media_type_found) { + throw new Exception(t('Only when media type allowed.')); + } + $media_type_found = TRUE; + } + else { + throw new Exception(t("Invalid value '@query_part' for breakpoint media query property.", array('@query_part' => $query_part))); + } + } + } + return TRUE; + } + return FALSE; + } +} diff --git a/core/modules/breakpoints/lib/Drupal/breakpoints/BreakpointSet.php b/core/modules/breakpoints/lib/Drupal/breakpoints/BreakpointSet.php new file mode 100644 index 0000000..d407421 --- /dev/null +++ b/core/modules/breakpoints/lib/Drupal/breakpoints/BreakpointSet.php @@ -0,0 +1,115 @@ +loadAllBreakpoints(); + } + + /** + * Overrides Drupal\Core\Entity::save(). + */ + public function save() { + // Only save the keys, but return the full objects. + $this->breakpoints = array_keys($this->breakpoints); + parent::save(); + $this->loadAllBreakpoints(); + } + + /** + * Override and save a breakpoint set. + */ + public function override() { + return entity_get_controller($this->entityType)->override($this); + } + + /** + * Revert a breakpoint set after it has been overridden. + */ + public function revert() { + return entity_get_controller($this->entityType)->revert($this); + } + + /** + * Implements EntityInterface::createDuplicate(). + */ + public function createDuplicate() { + $duplicate = new BreakpointSet(); + $duplicate->id = ''; + $duplicate->label = t('Clone of') . ' ' . $this->label(); + $duplicate->breakpoints = $this->breakpoints; + return $duplicate; + } + + /** + * Load all breakpoints, remove non-existing ones. + */ + protected function loadAllBreakpoints() { + $breakpoints = $this->breakpoints; + $this->breakpoints = array(); + foreach ($breakpoints as $breakpoint_id) { + $breakpoint = breakpoints_breakpoint_load($breakpoint_id); + if ($breakpoint) { + $this->breakpoints[$breakpoint_id] = $breakpoint; + } + } + } +} diff --git a/core/modules/breakpoints/lib/Drupal/breakpoints/BreakpointSetController.php b/core/modules/breakpoints/lib/Drupal/breakpoints/BreakpointSetController.php new file mode 100644 index 0000000..ae9c2ff --- /dev/null +++ b/core/modules/breakpoints/lib/Drupal/breakpoints/BreakpointSetController.php @@ -0,0 +1,59 @@ +sourceType == Breakpoint::BREAKPOINTS_SOURCE_TYPE_THEME) { + return FALSE; + } + foreach ($breakpointset->breakpoints as $key => $breakpoint) { + if ($breakpoint->sourceType == Breakpoint::BREAKPOINTS_SOURCE_TYPE_THEME && $breakpoint->source == $breakpointset->id()) { + $new_breakpoint = $breakpoint->createDuplicate(); + $new_breakpoint->id = ''; + $new_breakpoint->sourceType = Breakpoint::BREAKPOINTS_SOURCE_TYPE_CUSTOM; + $new_breakpoint->save(); + + // Remove old one, add new one. + unset($breakpointset->breakpoints[$key]); + $breakpointset->breakpoints[$new_breakpoint->id] = $new_breakpoint; + } + } + $breakpointset->overridden = TRUE; + $breakpointset->save(); + return $breakpointset; + } + + /** + * Revert a breakpoint set after it has been overridden. + */ + public function revert(BreakpointSet $breakpointset) { + if (!$breakpointset->overridden || !$breakpointset->sourceType == Breakpoint::BREAKPOINTS_SOURCE_TYPE_THEME) { + return FALSE; + } + + // Reload all breakpoints from theme. + $reloaded_set = breakpoints_breakpoints_set_reload_from_theme($breakpointset->id()); + if ($reloaded_set) { + $breakpointset->breakpoints = $reloaded_set->breakpoints; + $breakpointset->overridden = FALSE; + $breakpointset->save(); + } + return $breakpointset; + } +} diff --git a/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointSetCrudTest.php b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointSetCrudTest.php new file mode 100644 index 0000000..037e9db --- /dev/null +++ b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointSetCrudTest.php @@ -0,0 +1,75 @@ + 'Breakpoint Set CRUD operations', + 'description' => 'Test creation, loading, updating, deleting of breakpoint sets.', + 'group' => 'Breakpoints', + ); + } + + /** + * Test CRUD operations for breakpoint sets. + */ + public function testBreakpointSetCrud() { + // Add breakpoints. + $breakpoints = array(); + for ($i = 0; $i <= 3; $i++) { + $width = ($i + 1) * 200; + $values = array( + 'name' => drupal_strtolower($this->randomName()), + 'weight' => $i, + 'mediaQuery' => "(min-width: {$width}px)", + ); + $breakpoint = new Breakpoint($values); + $breakpoint->save(); + $breakpoints[$breakpoint->id()] = $breakpoint; + } + // Add a breakpoint set with minimum data only. + $label = $this->randomName(); + $values = array( + 'label' => $label, + 'id' => drupal_strtolower($label), + ); + + $set = new BreakpointSet($values); + $set->save(); + $this->verifyBreakpointSet($set); + + // Update the breakpoint set. + $set->breakpoints = array_keys($breakpoints); + $set->save(); + $this->verifyBreakpointSet($set); + + // Duplicate the breakpoint set. + $new_set = new BreakpointSet(); + $new_set->label = t('Clone of') . ' ' . $set->label(); + $new_set->id = ''; + $new_set->sourceType = Breakpoint::BREAKPOINTS_SOURCE_TYPE_CUSTOM; + $new_set->breakpoints = $set->breakpoints; + $duplicated_set = $set->createDuplicate(); + $this->verifyBreakpointSet($duplicated_set, $new_set); + + // Delete the breakpoint set. + $set->delete(); + $this->assertFalse(breakpoints_breakpointset_load($set->id), t('breakpoints_breakpointset_load: Loading a deleted breakpoint set returns false.'), t('Breakpoints API')); + } +} diff --git a/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointSetTestBase.php b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointSetTestBase.php new file mode 100644 index 0000000..28466a6 --- /dev/null +++ b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointSetTestBase.php @@ -0,0 +1,60 @@ +id) : $compare_set; + + foreach ($properties as $property) { + $t_args = array( + '%set' => $set->label(), + '%property' => $property, + ); + if (is_array($compare_set->{$property})) { + $this->assertEqual(array_keys($compare_set->{$property}), array_keys($set->{$property}), t('breakpoints_breakpoint_set_load: Proper %property for breakpoint set %set.', $t_args), $assert_set); + } + else { + $this->assertEqual($compare_set->{$property}, $set->{$property}, t('breakpoints_breakpoint_set_load: Proper %property . for breakpoint set %set.', $t_args), $assert_set); + } + } + } +} diff --git a/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsApiTest.php b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsApiTest.php new file mode 100644 index 0000000..b7e25ab --- /dev/null +++ b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsApiTest.php @@ -0,0 +1,90 @@ + 'Breakpoints general API functions', + 'description' => 'Test general API functions of the breakpoints module.', + 'group' => 'Breakpoints', + ); + } + + /** + * Test Breakpoint::buildConfigName(). + */ + public function testConfigName() { + $breakpoint = new Breakpoint( + array( + 'label' => drupal_strtolower($this->randomName()), + 'source' => 'custom_module', + // Try an invalid sourceType. + 'sourceType' => 'oops', + ) + ); + + try { + $breakpoint->save(); + } + catch (Exception $e) { + $exception = TRUE; + } + $this->assertTrue($exception, t('breakpoints_breakpoint_config_name: An exception is thrown when an invalid sourceType is entered.'), t('Breakpoints API')); + $this->assertEqual((string) $breakpoint->id(), '', t('breakpoints_breakpoint_config_name: No id is set when an invalid sourceType is entered.'), t('Breakpoints API')); + + // Try an invalid source. + $breakpoint->sourceType = BREAKPOINTS_SOURCE_TYPE_CUSTOM; + $breakpoint->source = 'custom*_module source'; + $exception = FALSE; + try { + $breakpoint->save(); + } + catch (Exception $e) { + $exception = TRUE; + } + $this->assertTrue($exception, t('breakpoints_breakpoint_config_name: An exception is thrown when an invalid source is entered.'), t('Breakpoints API')); + $this->assertEqual((string) $breakpoint->id(), '', t('breakpoints_breakpoint_config_name: No id is set when an invalid sourceType is entered.'), t('Breakpoints API')); + + // Try an invalid name (make sure there is at least once capital letter). + $breakpoint->source = 'custom_module'; + $breakpoint->name = drupal_ucfirst($this->randomName()); + $exception = FALSE; + try { + $breakpoint->save(); + } + catch (Exception $e) { + $exception = TRUE; + } + $this->assertTrue($exception, t('breakpoints_breakpoint_config_name: An exception is thrown when an invalid name is entered.'), t('Breakpoints API')); + $this->assertEqual((string) $breakpoint->id(), '', t('breakpoints_breakpoint_config_name: No id is set when an invalid sourceType is entered.'), t('Breakpoints API')); + + // Try a valid breakpoint. + $breakpoint->name = drupal_strtolower($this->randomName()); + $breakpoint->mediaQuery = 'all'; + $exception = FALSE; + try { + $breakpoint->save(); + } + catch (Exception $e) { + $exception = TRUE; + } + $this->assertFalse($exception, t('breakpoints_breakpoint_config_name: No exception is thrown when a valid breakpoint is passed.'), t('Breakpoints API')); + $this->assertEqual($breakpoint->id(), Breakpoint::BREAKPOINTS_SOURCE_TYPE_CUSTOM . '.custom_module.' . $breakpoint->name, t('breakpoints_breakpoint_config_name: A id is set when a valid breakpoint is passed.'), t('Breakpoints API')); + } +} diff --git a/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsCrudTest.php b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsCrudTest.php new file mode 100644 index 0000000..30d741b --- /dev/null +++ b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsCrudTest.php @@ -0,0 +1,63 @@ + 'Breakpoints CRUD operations', + 'description' => 'Test creation, loading, updating, deleting of breakpoints.', + 'group' => 'Breakpoints', + ); + } + + /** + * Test CRUD operations for breakpoints. + */ + public function testBreakpointsCrud() { + // Add a breakpoint with minimum data only. + $values = array( + 'label' => drupal_strtolower($this->randomName()), + 'mediaQuery' => '(min-width: 600px)', + ); + + $breakpoint = new Breakpoint($values); + $breakpoint->save(); + + $this->verifyBreakpoint($breakpoint); + + // Test breakpoints_breakpoint_load_all + $all_breakpoints = breakpoints_breakpoint_load_all(); + $config_name = $breakpoint->getConfigName(); + $this->assertTrue(isset($all_breakpoints[$config_name]), t('breakpoints_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); + + // Disable the breakpoint. + $breakpoint->disable(); + $this->verifyBreakpoint($breakpoint); + + // Delete the breakpoint. + $breakpoint->delete(); + $this->assertFalse(breakpoints_breakpoint_load($config_name), t('breakpoints_breakpoint_load: Loading a deleted breakpoint returns false.'), t('Breakpoints API')); + } +} diff --git a/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsMediaQueryTest.php b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsMediaQueryTest.php new file mode 100644 index 0000000..2c82831 --- /dev/null +++ b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsMediaQueryTest.php @@ -0,0 +1,121 @@ + 'Breakpoints media query tests', + 'description' => 'Test validation of media queries.', + 'group' => 'Breakpoints', + ); + } + + /** + * 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)', + '(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 (min-width)', + ); + + foreach ($media_queries as $media_query) { + try { + $this->assertTrue(Breakpoint::isValidMediaQuery($media_query), $media_query . ' is valid.'); + } + catch (Exception $e) { + $this->assertTrue(FALSE, $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 (min-orientation: landscape)', + 'screen and (max-orientation: landscape)', + 'screen and (orientation: bogus)', + '(orientation: bogus)', + ); + + foreach ($media_queries as $media_query) { + try { + $this->assertFalse(Breakpoint::isValidMediaQuery($media_query), $media_query . ' is not valid.'); + } + catch (Exception $e) { + $this->assertTrue(TRUE, $media_query . ' is not valid.'); + } + } + } +} diff --git a/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsMediaqueryTest.php b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsMediaqueryTest.php new file mode 100644 index 0000000..23f29fa --- /dev/null +++ b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsMediaqueryTest.php @@ -0,0 +1,117 @@ + 'Breakpoints media query tests', + 'description' => 'Test validation of media queries.', + 'group' => 'Breakpoints', + ); + } + + /** + * Test valid media queries. + */ + function testValidMediaQueries() { + $media_queries = array( + // Bartik + '(min-width: 0px)', + 'all and (min-width: 560px) and (max-width:850px)', + 'all and (min-width: 851px)', + // Seven + '(min-width: 0em)', + 'screen and (min-width: 40em)', + // Stark + '(min-width: 0px)', + 'all and (min-width: 480px) and (max-width: 959px)', + 'all and (min-width: 960px)', + '(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 (min-width)', + ); + + foreach($media_queries as $media_query) { + try { + $this->assertTrue(Breakpoint::isValidMediaQuery($media_query), $media_query . ' is valid.'); + } + catch (Exception $e) { + $this->assertTrue(FALSE, $media_query . ' is valid.'); + } + } + } + + /** + * Test invalid media queries. + */ + 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 (min-orientation: landscape)', + 'screen and (max-orientation: landscape)', + 'screen and (orientation: bogus)', + '(orientation: bogus)', + ); + + foreach($media_queries as $media_query) { + try { + $this->assertFalse(Breakpoint::isValidMediaQuery($media_query), $media_query . ' is not valid.'); + } + catch (Exception $e) { + $this->assertTrue(TRUE, $media_query . ' is not valid.'); + } + } + } +} diff --git a/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsTestBase.php b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsTestBase.php new file mode 100644 index 0000000..074c7f7 --- /dev/null +++ b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsTestBase.php @@ -0,0 +1,56 @@ +getConfigName()) : $compare_breakpoint; + foreach ($properties as $property) { + $t_args = array( + '%breakpoint' => $breakpoint->label(), + '%property' => $property, + ); + $this->assertEqual($compare_breakpoint->{$property}, $breakpoint->{$property}, t('breakpoints_breakpoint_load: Proper %property for breakpoint %breakpoint.', $t_args), $assert_group); + } + } +} diff --git a/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsThemeTest.php b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsThemeTest.php new file mode 100644 index 0000000..b424078 --- /dev/null +++ b/core/modules/breakpoints/lib/Drupal/breakpoints/Tests/BreakpointsThemeTest.php @@ -0,0 +1,91 @@ + 'Breakpoint Theme functionality', + 'description' => 'Thoroughly test the breakpoints provided by a theme.', + 'group' => 'Breakpoints', + ); + } + + /** + * Drupal\simpletest\WebTestBase\setUp(). + */ + public function setUp() { + parent::setUp(); + theme_enable(array('breakpoints_test_theme')); + } + + /** + * Test the breakpoints provided by a theme. + */ + public function testThemeBreakpoints() { + debug(breakpoints_breakpointset_load_all()); + // Verify the breakpoint group for breakpoints_test_theme was created. + $breakpoint_set_obj = new BreakpointSet(); + $breakpoint_set_obj->label = 'Breakpoints test theme'; + $breakpoint_set_obj->id = 'breakpoints_test_theme'; + $breakpoint_set_obj->sourceType = Breakpoint::BREAKPOINTS_SOURCE_TYPE_THEME; + $breakpoint_set_obj->breakpoints = array( + 'theme.breakpoints_test_theme.mobile' => array(), + 'theme.breakpoints_test_theme.narrow' => array(), + 'theme.breakpoints_test_theme.wide' => array(), + 'theme.breakpoints_test_theme.tv' => array(), + ); + $breakpoint_set_obj->overridden = 0; + + // Verify we can load this breakpoint defined by the theme. + $this->verifyBreakpointSet($breakpoint_set_obj); + + // Override the breakpoints. + $overridden_set = clone $breakpoint_set_obj; + $breakpoint_set = breakpoints_breakpointset_load('breakpoints_test_theme'); + $breakpoint_set = $breakpoint_set->override(); + + // Verify the group is overridden. + $overridden_set->breakpoints = array( + 'custom.breakpoints_test_theme.mobile' => array(), + 'custom.breakpoints_test_theme.narrow' => array(), + 'custom.breakpoints_test_theme.wide' => array(), + 'custom.breakpoints_test_theme.tv' => array(), + ); + $overridden_set->overridden = 1; + $this->verifyBreakpointSet($overridden_set); + + // Revert the breakpoint set. + $breakpoint_set = breakpoints_breakpointset_load('breakpoints_test_theme'); + $breakpoint_set = $breakpoint_set->revert(); + + // Verify the breakpointset has its original values again when loaded. + $this->verifyBreakpointSet($breakpoint_set_obj); + + // Disable the test theme and verify the breakpoint group is deleted. + theme_disable(array('breakpoints_test_theme')); + $this->assertFalse(breakpoints_breakpointset_load('breakpoints_test_theme'), t('breakpoints_breakpoint_group_load: Loading a deleted breakpoint group returns false.'), t('Breakpoints API')); + } +} diff --git a/core/modules/breakpoints/tests/breakpoints_theme_test.info b/core/modules/breakpoints/tests/breakpoints_theme_test.info new file mode 100644 index 0000000..4aa97c7 --- /dev/null +++ b/core/modules/breakpoints/tests/breakpoints_theme_test.info @@ -0,0 +1,5 @@ +name = Breakpoints Theme Test +description = Test breakpoints provided by themes +package = Other +core = 8.x +hidden = TRUE diff --git a/core/modules/breakpoints/tests/breakpoints_theme_test.module b/core/modules/breakpoints/tests/breakpoints_theme_test.module new file mode 100644 index 0000000..6f74ad2 --- /dev/null +++ b/core/modules/breakpoints/tests/breakpoints_theme_test.module @@ -0,0 +1,13 @@ +