diff --git js/skinr_ui.rules.js js/skinr_ui.rules.js
new file mode 100644
index 0000000..608257b
--- /dev/null
+++ js/skinr_ui.rules.js
@@ -0,0 +1,59 @@
+// $Id$
+(function ($) {
+
+/**
+ * Provide the summary information for the block settings vertical tabs.
+ */
+Drupal.behaviors.skinrUIRuleSettingsSummary = {
+  attach: function (context) {
+    // The drupalSetSummary method required for this behavior is not available
+    // on the Blocks administration page, so we need to make sure this
+    // behavior is processed only if drupalSetSummary is defined.
+    if (typeof jQuery.fn.drupalSetSummary == 'undefined') {
+      return;
+    }
+
+    $('fieldset#edit-path', context).drupalSetSummary(function (context) {
+      if (!$('textarea[name="pages"]', context).val()) {
+        return Drupal.t('Not restricted');
+      }
+      else {
+        return Drupal.t('Restricted to certain pages');
+      }
+    });
+
+    $('fieldset#edit-node-type', context).drupalSetSummary(function (context) {
+      var vals = [];
+      $('input[type="checkbox"]:checked', context).each(function () {
+        vals.push($.trim($(this).next('label').text()));
+      });
+      if (!vals.length) {
+        vals.push(Drupal.t('Not restricted'));
+      }
+      return vals.join(', ');
+    });
+
+    $('fieldset#edit-role', context).drupalSetSummary(function (context) {
+      var vals = [];
+      $('input[type="checkbox"]:checked', context).each(function () {
+        vals.push($.trim($(this).next('label').text()));
+      });
+      if (!vals.length) {
+        vals.push(Drupal.t('Not restricted'));
+      }
+      return vals.join(', ');
+    });
+
+    $('fieldset#edit-user', context).drupalSetSummary(function (context) {
+      var $radio = $('input[name="custom"]:checked', context);
+      if ($radio.val() == 0) {
+        return Drupal.t('Not customizable');
+      }
+      else {
+        return $radio.next('label').text();
+      }
+    });
+  }
+};
+
+})(jQuery);
diff --git skinr.install skinr.install
index a7af7a0..f2a0882 100644
--- skinr.install
+++ skinr.install
@@ -78,6 +78,13 @@ function skinr_schema() {
         'not null' => TRUE,
         'default' => '',
       ),
+      'node_types' => array(
+        'type' => 'text',
+        'size' => 'normal',
+        'not null' => FALSE,
+        'serialize' => TRUE,
+        'description' => 'A serialized array of node types for this record.',
+      ),
       'roles' => array(
         'type' => 'text',
         'size' => 'normal',
@@ -90,12 +97,12 @@ function skinr_schema() {
         'not null' => TRUE,
         'default' => 0,
         'size' => 'tiny',
-        'description' => 'Flag to indicate how to show on pages. (0 = Show on all pages except listed pages, 1 = Show only on listed pages, 2 = Use custom PHP code to determine visibility.)',
+        'description' => 'Flag to indicate how to show rules on pages. (0 = Show on all pages except listed pages, 1 = Show only on listed pages, 2 = Use custom PHP code to determine visibility)',
       ),
       'pages' => array(
         'type' => 'text',
         'not null' => TRUE,
-        'description' => 'Contents of the "Pages" block; contains either a list of paths on which to include/exclude the region or PHP code, depending on the visibility setting.',
+        'description' => 'Contains either a list of paths on which to include/exclude the rule or PHP code, depending on "visibility" setting.',
       ),
     ),
     'primary key' => array('rid'),
diff --git skinr.module skinr.module
index dfb9748..3030bde 100644
--- skinr.module
+++ skinr.module
@@ -11,6 +11,21 @@
 define('SKINR_VERSION', 2);
 
 /**
+ * Show this rule on every page except the listed pages.
+ */
+define('SKINR_RULE_VISIBILITY_NOTLISTED', 0);
+
+/**
+ * Show this rule on only the listed pages.
+ */
+define('SKINR_RULE_VISIBILITY_LISTED', 1);
+
+/**
+ * Show this rule if the associated PHP code returns TRUE.
+ */
+define('SKINR_RULE_VISIBILITY_PHP', 2);
+
+/**
  * Implements hook_help().
  */
 function skinr_help($path, $arg) {
@@ -180,6 +195,44 @@ function skinr_flatten_skins_array($skin_options) {
 // Rule functions.
 
 /**
+ * Validate a rule object.
+ *
+ * @param $rule
+ *   A rule object.
+ *
+ * @return
+ *   TRUE on success, FALSE on failure.
+ */
+
+function skinr_rule_validate(&$rule) {
+  if (empty($rule->title) || empty($rule->rule_type)) {
+    return FALSE;
+  }
+
+  if (!isset($rule->node_types)) {
+    $rule->node_types = array();
+  }
+  if (!isset($rule->roles)) {
+    $rule->roles = array();
+  }
+  if (!isset($rule->visibility)) {
+    $rule->visibility = 0;
+  }
+  if (!isset($rule->pages)) {
+    $rule->pages = '';
+  }
+
+  if (!is_array($rule->node_types) || !is_array($rule->roles)) {
+    return FALSE;
+  }
+  if ($rule->visibility !== 0 && $rule->visibility !== 1 && $rule->visibility !== 2) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/**
  * Save a skinr rule object.
  *
  * @param $rule
@@ -189,8 +242,13 @@ function skinr_flatten_skins_array($skin_options) {
  *   The rule ID.
  */
 function skinr_rule_save($rule) {
-  drupal_write_record('skinr_rules', $rule, !empty($rule->rid) ? array('rid') : array());
-  return $rule->rid;
+  // Make sure we're getting valid data.
+  if (!skinr_rule_validate($rule)) {
+    return FALSE;
+  }
+
+  $status = drupal_write_record('skinr_rules', $rule, !empty($rule->rid) ? array('rid') : array());
+  return $status;
 }
 
 /**
@@ -232,6 +290,7 @@ function skinr_rule_load_multiple($rids = array(), $conditions = array()) {
     $select->condition($field, $condition);
   }
   foreach ($select->execute() as $rule) {
+    $rule->node_types = unserialize($rule->node_types);
     $rule->roles = unserialize($rule->roles);
     $rules[$rule->rid] = $rule;
   }
@@ -257,44 +316,95 @@ function skinr_rule_delete($rid) {
 }
 
 /**
- * Determine whether a rule is applies to the current page or not.
+ * Determines if the rule should be visible for a given path.
  *
  * @param $rid
  *   The rule ID.
+ * @param $path
+ *   (optional) The path to check. Defaults to the path of the current page.
+ * @param $account
+ *   (optional) The account to check. Defaults to currently logged in user.
  *
- * @todo Rename this function to skinr_rule_is_visible().
+ * @return
+ *   TRUE if the rule should be visible, FALSE otherwise.
  */
-function skinr_rule_visible($rid) {
+function skinr_rule_is_visible($rid, $path = NULL, $account = NULL) {
   global $user;
 
+  if (!isset($account)) {
+    $account = $user;
+  }
+
   if ($rule = skinr_rule_load($rid)) {
-    $page_match = TRUE;
+    if (!isset($path)) {
+      $path = $_GET['q'];
+    }
 
-    if (!empty($record['roles']) && ($user->uid != 1) && !count(array_intersect(array_keys($user->roles), $rule->roles))) {
+    // Check role visibility.
+    if (!empty($rule->roles) && ($account->uid != 1) && !count(array_intersect(array_keys($account->roles), $rule->roles))) {
       return FALSE;
     }
 
-    // Match path if necessary
+    // Check content type visibility.
+    // If a rule has no node types associated, it is displayed for every type.
+    // For rules with node types associated, if the node type does not match
+    // the settings from this rule, return FALSE.
+    if (!empty($rule->node_types)) {
+      $node = menu_get_object('node', 1, $path);
+      $node_types = node_type_get_types();
+      if (arg(0, $path) == 'node' && arg(1, $path) == 'add' && arg(2, $path)) {
+        $node_add_arg = strtr(arg(2), '-', '_');
+      }
+
+      if (!empty($node)) {
+        // This is a node or node edit page.
+        if (empty($rule->node_types[$node->type])) {
+          // This rule should not be displayed for this node type.
+          return FALSE;
+        }
+      }
+      elseif (isset($node_add_arg) && isset($node_types[$node_add_arg])) {
+        // This is a node creation page.
+        if (!isset($rule->node_types[$node_add_arg]) || !$rule->node_types[$node_add_arg]) {
+          // This rule should not be displayed for this node type.
+          return FALSE;
+        }
+      }
+      else {
+        // This is not a node page, remove the rule.
+        return FALSE;
+      }
+    }
+
+    // Match path if necessary.
     if ($rule->pages) {
-      if ($rule->visibility < 2) {
-        $path = drupal_get_path_alias($_GET['q']);
-        // Compare with the internal and path alias (if any).
-        $page_match = drupal_match_path($path, $rule->pages);
+      // Convert path to lowercase. This allows comparison of the same path
+      // with different case. Ex: /Page, /page, /PAGE.
+      $pages = drupal_strtolower($rule->pages);
+      if ($rule->visibility < SKINR_RULE_VISIBILITY_PHP) {
+        // Convert the Drupal path to lowercase
+        $path = drupal_strtolower(drupal_get_path_alias($path));
+        // Compare the lowercase internal and lowercase path alias (if any).
+        $page_match = drupal_match_path($path, $pages);
         if ($path != $_GET['q']) {
-          $page_match = $page_match || drupal_match_path($_GET['q'], $rule->pages);
+          $page_match = $page_match || drupal_match_path($path, $pages);
         }
-        // When $rule->visibility has a value of 0, the item is displayed on
-        // all pages except those listed in $rule->pages. When set to 1, it
-        // is displayed only on those pages listed in $rule->pages.
+        // When $rule->visibility has a value of 0 (SKINR_RULE_VISIBILITY_NOTLISTED),
+        // the rule is displayed on all pages except those listed in $rule->pages.
+        // When set to 1 (SKINR_RULE_VISIBILITY_LISTED), it is displayed only on those
+        // pages listed in $rule->pages.
         $page_match = !($rule->visibility xor $page_match);
       }
+      elseif (module_exists('php')) {
+        $page_match = php_eval($rule->pages);
+      }
       else {
-        // PHP.
-        if (module_exists('php')) {
-          $page_match = php_eval($rule->pages);
-        }
+        $page_match = FALSE;
       }
     }
+    else {
+      $page_match = TRUE;
+    }
     return $page_match;
   }
   return FALSE;
diff --git skinr.skinr.inc skinr.skinr.inc
index fc260e8..da17239 100644
--- skinr.skinr.inc
+++ skinr.skinr.inc
@@ -147,7 +147,7 @@ function rules_skinr_preprocess_index_handler(&$variables) {
   // Find any page level skinr options and return an array of them.
   $indices = array();
   foreach ($rules as $rule) {
-    if (skinr_rule_visible($rule->rid)) {
+    if (skinr_rule_is_visible($rule->rid)) {
       $indices[] = $rule->rid;
     }
   }
@@ -185,7 +185,7 @@ function rules_skinr_contextual_links(&$variables) {
   $counter = 1;
 
   foreach ($rules as $rule) {
-    if (skinr_rule_visible($rule->rid)) {
+    if (skinr_rule_is_visible($rule->rid)) {
       $links['skinr-rule-' . $counter++] = array(
         'admin/config/skinr/edit/nojs', array('page', $rule->rid),
       );
diff --git skinr_ui.rules.inc skinr_ui.rules.inc
index 1d83f11..2a217e7 100644
--- skinr_ui.rules.inc
+++ skinr_ui.rules.inc
@@ -104,8 +104,9 @@ function skinr_rule_add($form, &$form_state) {
 function skinr_rule_add_submit($form, &$form_state) {
   $rule = new stdClass();
   $rule->rid = NULL;
-  $rule->rule_type = $form_state['values']['rule']['rule_type'];
   $rule->title = $form_state['values']['rule']['title'];
+  $rule->rule_type = $form_state['values']['rule']['rule_type'];
+  $rule->node_types = array();
   $rule->roles = array();
   $rule->visibility = 0;
   $rule->pages = '';
@@ -118,18 +119,16 @@ function skinr_rule_add_submit($form, &$form_state) {
 }
 
 /**
- * Menu callback; displays the edit form for a skinr rule.
+ * Form builder for the rule configuration form.
  *
  * @param $rid
  *   The rule ID.
  *
+ * @see skinr_rule_edit_submit()
  * @ingroup forms
  */
 function skinr_rule_edit($form, &$form_state, $rid = NULL) {
   $form['rule'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Skinr rule visibility'),
-    '#collapsible' => TRUE,
     '#weight' => -1,
   );
 
@@ -137,6 +136,7 @@ function skinr_rule_edit($form, &$form_state, $rid = NULL) {
     $rule = array(
       'title' => $form_state['values']['title'],
       'rule_type' => $form_state['values']['rule_type'],
+      'node_types' => $form_state['values']['types'],
       'roles' => $form_state['values']['roles'],
       'visibility' => $form_state['values']['visibility'],
       'pages' => $form_state['values']['pages'],
@@ -153,6 +153,7 @@ function skinr_rule_edit($form, &$form_state, $rid = NULL) {
     $rule = array(
       'title' => '',
       'rule_type' => '',
+      'node_types' => array(),
       'roles' => array(),
       'visibility' => 0,
       'pages' => '',
@@ -161,7 +162,7 @@ function skinr_rule_edit($form, &$form_state, $rid = NULL) {
 
   $form['rule']['title'] = array(
     '#type' => 'textfield',
-    '#title' => t('Title'),
+    '#title' => t('Rule title'),
     '#default_value' => $rule['title'],
     '#description' => t('Descriptive title for this rule; used by administrators.'),
     '#required' => TRUE,
@@ -173,49 +174,115 @@ function skinr_rule_edit($form, &$form_state, $rid = NULL) {
   );
   $form['rule']['rule_type_displayed'] = array(
     '#type' => 'item',
-    '#title' => t('Type'),
+    '#title' => t('Rule type'),
     '#markup' => $rule['rule_type'],
     '#description' => t('Type of element the rule is applied to.'),
   );
 
-  // Inject the rule's form elements here.
-  $form['rule']['roles'] = array(
-    '#type' => 'checkboxes',
-    '#title' => t('Show for specific roles'),
-    '#default_value' => $rule['roles'],
-    '#options' => user_roles(),
-    '#description' => t('Show only for the selected role(s). If you select no roles, it will be visible to all users.'),
+  // Visibility settings.
+  $form['visibility_title'] = array(
+    '#type' => 'item',
+    '#title' => t('Visibility settings'),
+  );
+  $form['visibility'] = array(
+    '#type' => 'vertical_tabs',
+    '#attached' => array(
+      'js' => array(drupal_get_path('module', 'skinr_ui') . '/js/skinr_ui.rules.js'),
+    ),
   );
 
-  // Set up options and description.
-  $options = array(0 => t('Show on every page except the listed pages.'), 1 => t('Show on only the listed pages.'));
-  $description = t("Enter one page per line as Drupal paths. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '<front>'));
+  // Per-path visibility.
+  $form['visibility']['path'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Pages'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#group' => 'visibility',
+    '#weight' => 0,
+  );
 
-  // Add the PHP specific stuff, if user has access.
-  if (module_exists('php') && user_access('use PHP for visibility')) {
-    $options[2] = t('Show if the following PHP code returns <code>TRUE</code> (PHP-mode, experts only).');
-    $description .= ' '. t('If the PHP-mode is chosen, enter PHP code between %php. Note that executing incorrect PHP-code can break your Drupal site.', array('%php' => '<?php ?>'));
+  $access = user_access('use PHP for settings');
+  if (isset($rule['visibility']) && $rule['visibility'] == SKINR_RULE_VISIBILITY_PHP && !$access) {
+    $form['visibility']['path']['visibility'] = array(
+      '#type' => 'value',
+      '#value' => SKINR_RULE_VISIBILITY_PHP,
+    );
+    $form['visibility']['path']['pages'] = array(
+      '#type' => 'value',
+      '#value' => isset($rule['pages']) ? $rule['pages'] : '',
+    );
   }
-  $form['rule']['visibility'] = array(
-    '#type' => 'radios',
-    '#title' => t('Show on specific pages'),
-    '#options' => $options,
-    '#default_value' => $rule['visibility'],
-    '#required' => TRUE,
+  else {
+    $options = array(
+      SKINR_RULE_VISIBILITY_NOTLISTED => t('All pages except those listed'),
+      SKINR_RULE_VISIBILITY_LISTED => t('Only the listed pages'),
+    );
+    $description = t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '<front>'));
+
+    if (module_exists('php') && $access) {
+      $options += array(SKINR_RULE_VISIBILITY_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'));
+      $title = t('Pages or PHP code');
+      $description .= ' ' . t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', array('%php' => '<?php ?>'));
+    }
+    else {
+      $title = t('Pages');
+    }
+    $form['visibility']['path']['visibility'] = array(
+      '#type' => 'radios',
+      '#title' => t('Show block on specific pages'),
+      '#options' => $options,
+      '#default_value' => isset($rule['visibility']) ? $rule['visibility'] : SKINR_RULE_VISIBILITY_NOTLISTED,
+    );
+    $form['visibility']['path']['pages'] = array(
+      '#type' => 'textarea',
+      '#title' => '<span class="element-invisible">' . $title . '</span>',
+      '#default_value' => isset($rule['pages']) ? $rule['pages'] : '',
+      '#description' => $description,
+    );
+  }
+
+  // Per-node visbility.
+  $form['visibility']['node_type'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Content types'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#group' => 'visibility',
+    '#weight' => 5,
   );
-  $form['rule']['pages'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Pages'),
-    '#default_value' => $rule['pages'],
-    '#description' => $description,
+  $form['visibility']['node_type']['types'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Show block for specific content types'),
+    '#default_value' => $rule['node_types'],
+    '#options' => node_type_get_names(),
+    '#description' => t('Show this block only on pages that display content of the given type(s). If you select no types, there will be no type-specific limitation.'),
   );
 
-  $form['buttons']['save'] = array(
+  // Per-role visibility.
+  $role_options = array_map('check_plain', user_roles());
+  $form['visibility']['role'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Roles'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#group' => 'visibility',
+    '#weight' => 10,
+  );
+  $form['visibility']['role']['roles'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Show block for specific roles'),
+    '#default_value' => $rule['roles'],
+    '#options' => $role_options,
+    '#description' => t('Show this rule only for the selected role(s). If you select no roles, the rule will be visible to all users.'),
+  );
+
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array(
     '#type' => 'submit',
-    '#value' => t('Save'),
+    '#value' => t('Save rule'),
   );
   if (isset($rule['rid'])) {
-    $form['buttons']['delete'] = array(
+    $form['actions']['delete'] = array(
       '#type' => 'submit',
       '#value' => t('Delete'),
       '#submit' => array('skinr_rule_delete_submit'),
@@ -226,16 +293,19 @@ function skinr_rule_edit($form, &$form_state, $rid = NULL) {
 }
 
 /**
- * Process skinr_rule_edit() submissions.
+ * Form submission handler for the rule configuration form.
+ *
+ * @see skinr_rule_edit()
  */
 function skinr_rule_edit_submit($form, &$form_state) {
   $rule = new stdClass();
   $rule->rid = !empty($form_state['values']['rid']) ? $form_state['values']['rid'] : NULL;
   $rule->rule_type = $form_state['values']['rule_type'];
   $rule->title = $form_state['values']['title'];
+  $rule->node_types = array_filter($form_state['values']['types']);
   $rule->roles = $form_state['values']['roles'];
-  $rule->visibility = $form_state['values']['visibility'];
-  $rule->pages = $form_state['values']['pages'];
+  $rule->visibility = (int) $form_state['values']['visibility'];
+  $rule->pages = trim($form_state['values']['pages']);
 
   skinr_rule_save($rule);
   // Set rule id, if we inserted a new rule to allow others to know what rule they're working with.
@@ -250,7 +320,7 @@ function skinr_rule_edit_submit($form, &$form_state) {
  * @ingroup forms
  */
 function skinr_rule_delete_submit($form, &$form_state) {
-  $destination = '';
+  $destination = array();
   if (isset($_REQUEST['destination'])) {
     $destination = drupal_get_destination();
     unset($_REQUEST['destination']);
diff --git tests/skinr.test tests/skinr.test
index defde6c..6807390 100644
--- tests/skinr.test
+++ tests/skinr.test
@@ -96,8 +96,7 @@ class SkinrApiTestCase extends DrupalWebTestCase {
   }
 
   function setUp() {
-    parent::setUp(array('skinr', 'skinr_test', 'skinr_test_incompatible'));
-    // Enable skinr_test_subtheme, but NOT the basetheme.
+    parent::setUp(array('skinr', 'skinr_test', 'skinr_test_incompatible'));    // Enable skinr_test_subtheme, but NOT the basetheme.
     theme_enable(array('skinr_test_subtheme'));
   }
 
@@ -283,4 +282,183 @@ class SkinrApiTestCase extends DrupalWebTestCase {
     // After asserting all expected, the list of skins should be empty.
     $this->assertTrue(empty($config), 'No unexpected skins found.');
   }
+
+  /**
+   * Tests loading and saving of rules.
+   */
+  public function testSkinrRulesLoadSave() {
+    // Test saving a rule.
+    $rule = (object) array(
+      'title' => 'Rule 1',
+      'rule_type' => 'page',
+      'node_types' => array(),
+      'roles' => array(),
+      'visibility' => 0, // Show on all pages, except those listed.
+      'pages' => '',
+    );
+    $this->assertTrue(skinr_rule_save($rule), 'Rule object was saved when no filtering is applied.');
+
+    $rule->title = '';
+    $this->assertFalse($status = skinr_rule_save($rule), 'Rule object was not saved when the required $rule->title field was empty.');
+    $this->pass('Status: ' . ($status ? 'true' : 'false'));
+    $rule->title = 'Rule 1';
+
+    $rule->rule_type = '';
+    $this->assertFalse(skinr_rule_save($rule), 'Rule object was not saved when the required $rule->rule_type field was empty.');
+    $rule->rule_type = 'page';
+
+    $rule->node_types = FALSE;
+    $this->assertFalse(skinr_rule_save($rule), 'Rule object was not saved when $rule->node_types was not an array.');
+    $rule->node_types = array();
+
+    $rule->roles = FALSE;
+    $this->assertFalse(skinr_rule_save($rule), 'Rule object was not saved when $rule->roles was not an array.');
+    $rule->roles = array();
+
+    // Test loading a rule.
+    $loaded_rule = skinr_rule_load($rule->rid);
+    $this->assertTrue(is_array($loaded_rule->node_types), 'Node types for the rule object were unserialized.');
+    $this->assertTrue(is_array($loaded_rule->roles), 'Roles for the rule object were unserialized.');
+
+    $this->assertTrue($loaded_rule->title == $rule->title && $loaded_rule->rule_type == $rule->rule_type && $loaded_rule->node_types == $rule->node_types && $loaded_rule->roles == $rule->roles && $loaded_rule->visibility == $rule->visibility && $loaded_rule->pages == $rule->pages, 'Rule object was loaded properly.');
+
+    // Save a second rule.
+    $second_rule = (object) array(
+      'title' => 'Rule 2',
+      'rule_type' => 'page',
+      'node_types' => array(),
+      'roles' => array(),
+      'visibility' => 0, // Show on all pages, except those listed.
+      'pages' => '',
+    );
+    skinr_rule_save($second_rule);
+
+    // Test loading multiple skin configurations.
+    $rules = skinr_rule_load_multiple(array($rule->rid, $second_rule->rid));
+    $this->assertTrue(count($rules) == 2 && isset($rules[$rule->rid]->rid) && isset($rules[$second_rule->rid]->rid), 'Successfully loaded multiple rules.');
+
+    // Test loading all skin configurations.
+    $rules = skinr_rule_load_multiple();
+    $this->assertTrue(count($rules) == 2 && isset($rules[$rule->rid]->rid) && isset($rules[$second_rule->rid]->rid), 'Successfully loaded all rules.');
+  }
+}
+
+/**
+ * Tests API functionality.
+ */
+class SkinrRulesApiTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Rules API',
+      'description' => 'Tests Skinr Rules API functionality.',
+      'group' => 'Skinr',
+    );
+  }
+
+  function setUp() {
+    parent::setUp(array('skinr'));
+
+    // Enable php module.
+    module_enable(array('php'));
+
+    // Set up some nodes.
+    $this->nodes['article'] = $this->drupalCreateNode(array('type' => 'article', 'title' => 'Article node'));
+    $this->nodes['page']    = $this->drupalCreateNode(array('type' => 'page', 'title' => 'Page node'));
+
+    // Set up some users.
+    $this->users['normal_user'] = $this->drupalCreateUser(array());
+  }
+
+  /**
+   * Tests visibility of rules.
+   */
+  public function testSkinrRulesVisibility() {
+    $front = variable_get('site_frontpage', 'node');
+
+    $article = $this->nodes['article'];
+    $page    = $this->nodes['page'];
+
+    $rule = (object) array(
+      'title' => 'Rule 1',
+      'rule_type' => 'page',
+      'node_types' => array(),
+      'roles' => array(),
+      'visibility' => 0, // Show on all pages, except those listed.
+      'pages' => '',
+    );
+    skinr_rule_save($rule);
+
+    // Test visibility when no filters are applied.
+    $this->drupalGet('');
+    $this->assertTrue(skinr_rule_is_visible($rule->rid), 'The rule is visible on the front page.');
+
+    $this->drupalGet('node/' . $article->nid);
+    $this->assertTrue(skinr_rule_is_visible($rule->rid), 'The rule is visible for an article node.');
+
+    $this->drupalGet('node/' . $page->nid);
+    $this->assertTrue(skinr_rule_is_visible($rule->rid), 'The rule is visible for a basic page node.');
+
+    // Test visibility with a node type filter.
+    $rule->node_types = array('article' => 'article');
+    skinr_rule_save($rule);
+
+    $this->assertFalse(skinr_rule_is_visible($rule->rid, $front), "The rule is not visible on the front page when it has the node type filter set to 'article'.");
+    $this->assertTrue(skinr_rule_is_visible($rule->rid, 'node/' . $article->nid), "The rule is visible for an article node when it has the node type filter set to 'article'.");
+    $this->assertFalse(skinr_rule_is_visible($rule->rid, 'node/' . $page->nid), "The rule is not visible for a basic page node when it has the node type filter set to 'article'.");
+
+    // @todo Need to test with node/add/* path.
+    // Problem to solve: node_type_get_types() always returns an empty array when running tests.
+
+    // Test visibility with a roles filter.
+    $rule->node_types = array();
+    $rule->roles = array(2 => '2');
+    skinr_rule_save($rule);
+
+    // @todo Is there a way to test role API functionality without temporarily
+    //   changing global user?
+    global $user;
+    $current_user = $user;
+
+    $user = $this->users['normal_user'];
+    $this->assertTrue(skinr_rule_is_visible($rule->rid, $front), "The rule is visible for a logged-in user when it has a role filter set to 'authenticated user'.");
+
+    $user = drupal_anonymous_user();
+    $this->assertFalse(skinr_rule_is_visible($rule->rid, $front), "The rule is not visible for an anonymous user when it has a role filter set to 'authenticated user'.");
+
+    $user = $current_user;
+
+
+    // Test visibility with an exclude page filter.
+    $rule->roles = array();
+    $rule->pages = "<front>";
+    skinr_rule_save($rule);
+
+    $this->assertFalse(skinr_rule_is_visible($rule->rid, $front), "The rule is not visible on the front page when it has the page filter set to exclude '&lt;front&gt'.");
+    $this->assertTrue(skinr_rule_is_visible($rule->rid, 'node/' . $article->nid), "The rule is visible for an article node when it has the page filter set to exclude '&lt;front&gt'.");
+    $this->assertTrue(skinr_rule_is_visible($rule->rid, 'node/' . $page->nid), "The rule is visible for a basic page node when it has the page filter set to exclude '&lt;front&gt'.");
+
+    // Test visibility with an include page filter.
+    $rule->visibility = 1;
+    skinr_rule_save($rule);
+
+    $this->assertTrue(skinr_rule_is_visible($rule->rid, $front), "The rule is visible on the front page when it has the page filter set to include '&lt;front&gt'.");
+    $this->assertFalse(skinr_rule_is_visible($rule->rid, 'node/' . $article->nid), "The rule is not visible for an article node when it has the page filter set to include '&lt;front&gt'.");
+    $this->assertFalse(skinr_rule_is_visible($rule->rid, 'node/' . $page->nid), "The rule is not visible for a basic page node when it has the page filter set to include '&lt;front&gt'.");
+
+    // Test visibility with a php page filter.
+    $rule->visibility = 2;
+    $rule->pages = "<?php\nreturn FALSE;\n?>";
+    skinr_rule_save($rule);
+
+    $this->assertFalse(skinr_rule_is_visible($rule->rid, $front), "The rule is not visible on the front page when it has the page filter set to php with code always returning FALSE.");
+    $this->assertFalse(skinr_rule_is_visible($rule->rid, 'node/' . $article->nid), "The rule is not visible for an article node when it has the page filter set to php with code always returning FALSE.");
+
+    $rule->pages = "<?php\nreturn TRUE;\n?>";
+    skinr_rule_save($rule);
+
+    $this->assertTrue(skinr_rule_is_visible($rule->rid), "The rule is visible on the front page when it has the page filter set to php with code always returning TRUE.");
+    $this->assertTrue(skinr_rule_is_visible($rule->rid, 'node/' . $article->nid), "The rule is visible for an article node when it has the page filter set to php with code always returning TRUE.");
+  }
 }
diff --git tests/skinr_ui.test tests/skinr_ui.test
index b82a8e9..8d91b04 100644
--- tests/skinr_ui.test
+++ tests/skinr_ui.test
@@ -190,3 +190,54 @@ class SkinrUIAdminTestCase extends SkinrUITestCase {
     $this->assertText(t('Typography'));
   }
 }
+
+/**
+ * Tests rules administrative pages functionality.
+ */
+class SkinrUIRulesTestCase extends SkinrUITestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Rules UI',
+      'description' => 'Tests rules functionality for Skinr UI.',
+      'group' => 'Skinr',
+    );
+  }
+
+  /**
+   * Tests administrative interface for rules.
+   */
+  function testRules() {
+    // Test that there is a rules page.
+    $this->drupalGet('admin/appearance/skinr');
+    $this->assertLinkByHref('admin/appearance/skinr/rules');
+
+    // Test that there is a way to add rules.
+    $this->drupalGet('admin/appearance/skinr/rules');
+    $this->clickLink(t('Create a new rule'), 0);
+
+    // Verify that we end up on the expected URL.
+    $this->assertUrl('admin/appearance/skinr/rules/add');
+
+    // Verify that we can create the rule.
+    $edit = array(
+      'rule[title]' => 'Rule 1',
+      'rule[rule_type]' => 'page',
+    );
+    $this->drupalPost(NULL, $edit, t('Add'));
+
+    // After posting, we expect to be redirected to the rule edit page.
+    $this->assertUrl('admin/appearance/skinr/rules/edit/1');
+
+    // Save rule.
+    // @todo Add a skin and test whether it applies properly or not.
+    $edit = array(
+    );
+    $this->drupalPost(NULL, $edit, t('Save rule'));
+
+    // We should be returned back to the rules page.
+    $this->assertUrl('admin/appearance/skinr/rules');
+
+    // Make sure the new rule appears listed on this page.
+    $this->assertLinkByHref('admin/appearance/skinr/rules/edit/1');
+  }
+}

