diff --git a/role_expire.info b/role_expire.info
index f8b5f32..ca4f6ab 100644
--- a/role_expire.info
+++ b/role_expire.info
@@ -1,3 +1,11 @@
 name = Role Expire
 description = "Enables user roles to expire on given time and day."
-core = 7.x
\ No newline at end of file
+core = 7.x
+files[] = tests/role_expire.test
+
+; Information added by drupal.org packaging script on 2011-07-28
+version = "7.x-1.x-dev"
+core = "7.x"
+project = "role_expire"
+datestamp = "1311813523"
+
diff --git a/role_expire.module b/role_expire.module
index 29ef0f8..4557f39 100644
--- a/role_expire.module
+++ b/role_expire.module
@@ -184,7 +184,11 @@ function role_expire_get_expired($time = '') {
  * Implements hook_views_api().
  */
 function role_expire_views_api() {
-  return array("api" => views_api_version());
+  // We can't just return "what is the currently installed views API version!
+  // We have to specify which version of the API we designed this implementation
+  // around.
+  // return array("api" => views_api_version());
+  return array('api' => '3.0');
 }
 
 /**
@@ -305,24 +309,29 @@ function role_expire_user_update(&$edit, $account, $category) {
       }
     }
 
-    // This adds default expiration to any new roles that have been given to the user.
-    $new_roles = array_diff(array_keys($edit['roles']), array_keys($edit['original']->roles));
-    if (isset($new_roles)) {
-      // We have the new roles, loop over them and see whether we need to assign expiry to them.
-      foreach ($new_roles as $role_id) {
-        role_expire_process_default_role_duration_for_user($role_id, $account->uid);
+    if (isset($edit['roles'])) {
+
+      // Add default expiration to any new roles that have been given to the user.
+      $new_roles = array_diff(array_keys($edit['roles']), array_keys($edit['original']->roles));
+      if (isset($new_roles)) {
+        // We have the new roles, loop over them and see whether we need to assign expiry to them.
+        foreach ($new_roles as $role_id) {
+          role_expire_process_default_role_duration_for_user($role_id, $account->uid);
+        }
       }
-    }
-    // This removes expiration for roles that have been removed from the user.
-    $del_roles = array_diff(array_keys($edit['original']->roles), array_keys($edit['roles']));
-    if (isset($del_roles)) {
-      // We have the deleted roles, loop over them and remove their expiry info.
-      foreach ($del_roles as $role_id) {
-        role_expire_delete_record($account->uid, $role_id);
+
+      // Remove expiration for roles that have been removed from the user.
+      $del_roles = array_diff(array_keys($edit['original']->roles), array_keys($edit['roles']));
+      if (isset($del_roles)) {
+        // We have the deleted roles, loop over them and remove their expiry info.
+        foreach ($del_roles as $role_id) {
+          role_expire_delete_record($account->uid, $role_id);
+        }
       }
-    }
-  }
 
+    } // if edit[roles]
+
+  } // if category && user_access
 }
 
 
diff --git a/role_expire.views.inc b/role_expire.views.inc
index 8f5dde0..c089304 100644
--- a/role_expire.views.inc
+++ b/role_expire.views.inc
@@ -4,11 +4,26 @@
  * @file
  * Role Expire Views hooks
  *
- * Views module integration with the role expire module
+ * Views module integration with the role expire module. Exposes the following
+ * to Views:
+ *  Fields:
+ *    "Role expiration date/time" - the date/time that a role expires
+ *    "Role expiration role" - the role that expires at the given Role expiration date/time
+ *  Filters:
+ *    "Role expiration date/time"
+ *    "Role expiration role"
+ *  Arguments (aka "Contextual Filters"):
+ *    "Role expiration role" - on the querystring as a role ID, not as a role name.
+ *
+ * NOTE: The Views API hook hook_views_api must be defined in the main module.
+ * @see role_expire_views_api() in role_expire.module.
  */
 
+
 /**
  * Implementation of hook_views_data().
+ *
+ * The purpose of this hook is to tell Views what data we make available.
  */
 function role_expire_views_data() {
   $data['role_expire']['table']['group']  = t('User');
@@ -19,9 +34,11 @@ function role_expire_views_data() {
       'field' => 'uid',
     ),
   );
+
+  // Expose the role expiration date
   $data['role_expire']['expiry_timestamp'] = array(
-    'title' => t('Role expiration time'),
-    'help' => t('Time and date the role will expire.'),
+    'title' => t('Role expiration date/time'),
+    'help' => t('Date and time the role will expire. (See also Role expiration role.)'),
     'field' => array(
       'handler' => 'views_handler_field_date',
       'click sortable' => TRUE,
@@ -33,5 +50,72 @@ function role_expire_views_data() {
       'handler' => 'views_handler_filter_date',
     ),
   );
+
+  // Expose the role id from role_expire
+  $data['role_expire']['rid'] = array(
+    'title' => t('Role expiration role'),
+    'help' => t('The Role that corresponds with the Role expiration date/time'),
+    // Information for displaying the rid
+    'field' => array(
+      'handler' => 'role_expire_handler_field_rid',
+      'click sortable' => TRUE,
+    ),
+    // Information for accepting a rid as an argument
+    'argument' => array(
+      'handler' => 'views_handler_argument_users_roles_rid',
+      'name field' => 'title', // the field to display in the summary.
+      'numeric' => TRUE,
+      'validate type' => 'rid',
+    ),
+    // Information for accepting a uid as a filter
+    'filter' => array(
+      'handler' => 'views_handler_filter_user_roles',
+    ),
+    // Information for sorting on a uid.
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+  );
+
   return $data;
-}
\ No newline at end of file
+}
+
+class role_expire_handler_field_rid extends views_handler_field {
+
+  // Derived from views_handler_field_user_roles
+  // Purpose: get the *names* that correspond to the role_expire_rids.
+  function pre_render($values) {
+    $roles = array();
+    $this->items = array();
+
+    // Get all the unique role ids into the keys of $roles. Initializing into
+    // array_keys helps prevent us from having a list where the same rid appears
+    // over and over and over.
+    foreach ($values as $result) {
+      $roles[$this->get_value($result, NULL, TRUE)] = FALSE;
+    }
+
+    if ($roles) {
+      $result = db_query("SELECT r.rid, r.name FROM {role} r WHERE r.rid IN (:rids) ORDER BY r.name",
+        array(':rids' => array_keys($roles)));
+      foreach ($result as $role) {
+        $this->items[$role->rid]['role'] = check_plain($role->name);
+        $this->items[$role->rid]['rid'] = $role->rid;
+      }
+    }
+  }
+
+  // Render the rid as the role name.
+  function render($values) {
+
+    // Return the role name corresponding to the role ID.
+    // TODO: Should I be using this->get_value() here?
+    $rid = $values->role_expire_rid;
+    if ($rid) {
+      $role = $this->items[$rid]['role'];
+      if (!empty($role)) {
+        return $role;
+      }
+    }
+  }
+}
diff --git a/tests/role_expire.test b/tests/role_expire.test
new file mode 100644
index 0000000..641bffb
--- /dev/null
+++ b/tests/role_expire.test
@@ -0,0 +1,201 @@
+<?php
+
+///**
+// * @package RoleExpireUnitTestCase
+// */
+//class RoleExpireUnitTestCase extends DrupalUnitTestCase {
+//
+//public static function getInfo() {
+//
+//  return array(
+//    'name' => 'Role Expire unit tests',
+//    'description' => 'Unit tests for the Role Expire module.',
+//    'group' => 'Role Expire',
+//  );
+//
+//}
+//
+//
+//} // class RoleExpireUnitTestCase
+
+
+/**
+ * @package RoleExpireWebTestCase
+ */
+class RoleExpireWebTestCase extends DrupalWebTestCase {
+
+protected $privileged_user;
+
+public static function getInfo() {
+
+  return array(
+    'name' => 'Role Expire web tests',
+    'description' => 'Web tests for the Role Expire module.',
+    'group' => 'Role Expire',
+  );
+
+}
+
+public function setUp() {
+  parent::setUp('role_expire');  // Enable any modules required for the test
+
+  // Create and log in our privileged user. Note that drupalCreateUser also
+  // calls drupalCreateRole to create a new role and to assign it to the new
+  // user. The name of the new role is the same as its rid.
+  $this->privileged_user = $this->drupalCreateUser(array(
+    'administer permissions',
+    'administer role expire',
+    ));
+  $this->drupalLogin($this->privileged_user);
+}
+
+public function testRoleExpire_tests() {
+
+  // Verify that the user/edit page has the role_expire extensions on it.
+  $this->drupalGet('user/'.$this->privileged_user->uid.'/edit');
+
+  // Evaluate: does the user/edit page have role-expire stuff on it?
+  $str = 'role expiration time';
+  if (!$this->assertText(t($str),t('Testing for "%str" on page.',array('%str'=>$str)))) {
+    // This is a critical failure. Time to abort.
+    $this->error(t('Testing aborted: %str missing',array('%str'=>$str)));
+    return FALSE;
+  }
+
+  // NOTE: No need to test in the creation/deletion of users and roles. Let
+  // the *user* simpletests do that. NOTE: user.test takes a long time.
+  
+  // Create a role *without* a default expiration date.
+  // ***covered by user.test***
+
+  // Edit role: add and remove default expiration date to role
+  // (but not to 'authenticated user').
+  $roles = user_roles(TRUE);
+  $rid = max(array_keys($roles));
+  $rname = $roles[$rid];
+  if ($rname == t('authenticated user')) {
+    // Exception!
+    $this->error(t('Testing aborted before completion: no roles to test on.'));
+    return FALSE;
+  }
+
+  // Here's where we actually set and unset the default expiration for the role.
+  $field = 'role_expire';
+  foreach (array('12321','') as $str) {
+    $edit = array(
+        $field => $str,
+    );
+    $this->drupalPost('admin/people/permissions/roles/edit/'.$rid,$edit,t('Save role'));
+    $result = $this->drupalGet('admin/people/permissions/roles/edit/'.$rid);
+    $this->assertFieldByName($field,$str,
+            t('Looking for value "%str" in %field field',
+                    array('%str' => $str,
+                          '%field' => $field,
+                         )
+             )
+            );
+    
+  } // foreach field value
+
+  $field = 'role_expire_'.$rid;
+  $user_edit_path = 'user/'.$this->privileged_user->uid.'/edit';
+
+  // Edit user: add a date that's in the past.
+  $yesterday = date('Y-m-d H:i:s',strtotime('-1 day'));
+  $edit = array($field => $yesterday);
+  $result = $this->drupalPost($user_edit_path,$edit,t('Save'));
+  $this->assertText(t('Role expiry must be in the future.'),t('Yesterday should be invalid'));
+  
+  // Edit user: add completely invalid expiration date.
+  $invalid_date = 'completely invalid date';
+  $edit = array($field => $invalid_date);
+  $this->drupalPost($user_edit_path,$edit,t('Save'));
+  $this->assertText(t('Role expiry is not in correct format.'),t('Date must be a date'));
+
+  // Edit user: remove expiration date from a role.
+  $blank = '';
+  $edit = array($field => $blank);
+  $this->drupalPost($user_edit_path,$edit,t('Save'));
+  $this->assertNoRaw(t('error'),t('Blank date should be valid.'));
+
+  // Edit user: add valid (future) expiration date to a role.
+  $tomorrow = date('Y-m-d H:i:s',strtotime('+1 day'));
+  $edit = array($field => $tomorrow);
+  $this->drupalPost($user_edit_path,$edit,t('Save'));
+  $this->assertFieldByName($field,$tomorrow,
+          t('Tomorrow (%t) should be valid',array('%t' => $tomorrow))
+          );
+
+  // Edit user: Unassign a role that does not have an expiration date.
+  // *** Should be covered by user.test ***
+
+  // Edit user: Unassign a role from user that does have an expiration date.
+  // TODO: Why does the SimpleTest RoleExpireWebTestCase not show *any* of the roles
+  // after this test?
+  $checkbox = "roles[$rid]";
+  $edit = array($checkbox => FALSE);
+  $this->drupalPost($user_edit_path,$edit,t('Save'));
+  if (!$this->assertRaw($checkbox,t('Making sure role checkbox is still on page.'))) {
+    // This is a critical error!  Exception!!
+    $this->error(t('Testing aborted before completion: checkbox missing.'));
+    return FALSE;
+  }
+  $this->assertNoRaw(t('error'),t('Unassigning role with expiration should be OK.'));
+
+  // Delete a user with a role that does not have an expiration date.
+  // *** Should be covered by user.test ***
+
+  // Delete a user with a role that does have an expiration date.
+  $edit = array(
+            $checkbox => TRUE,
+            $field => $tomorrow,
+          );
+  $this->drupalPost($user_edit_path,$edit,t('Save'));
+  $this->assertText($rname,t('Looking for role name "%r" on page.',array('%r'=>$name)));
+  $this->assertNoRaw(t('error'),t('Re-adding role with expiration date.'));
+
+  // [The following adapted from user.test/testUserCancelByAdmin()]
+
+  // Create administrative user.
+  $admin_user = $this->drupalCreateUser(array('administer users'));
+  $this->drupalLogin($admin_user);
+
+  $this->drupalGet($user_edit_path);
+  $this->drupalPost(NULL, NULL, t('Cancel account'));
+
+  // Confirm deletion.
+  $this->drupalPost(NULL, NULL, t('Cancel account'));
+  $this->assertRaw(t('%name has been deleted.', array('%name' => $account->name)), t('User deleted.'));
+  $this->assertNoRaw(t('error'),t('Deleting user with a role that has an expiration date.'));
+
+  // Done!
+  return TRUE;
+}
+
+public function testAssignAndRemoveRole()  {
+
+  // Create and log in the operating user.
+  $admin_user = $this->drupalCreateUser(array('administer users','administer role expire'));
+  $this->drupalLogin($admin_user);
+
+  // Create the victim (with a randomly assigned role).
+  $victim_user = $this->drupalCreateUser();
+
+  // Get the victim's highest rid.
+  $all_rids = array_keys($victim_user->roles);
+  sort($all_rids);
+  $rid = array_pop($all_rids);
+
+  // Assign the role to the user.
+  $this->drupalPost('user/' . $victim_user->uid . '/edit', array("roles[$rid]" => $rid), t('Save'));
+  $this->assertText(t('The changes have been saved.'));
+  $this->assertFieldChecked('edit-roles-' . $rid, t('Role is assigned.'));
+
+  // Remove the role from the user.
+  $this->drupalPost('user/' . $victim_user->uid . '/edit', array("roles[$rid]" => FALSE), t('Save'));
+  $this->assertText(t('The changes have been saved.'));
+  $this->assertNoFieldChecked('edit-roles-' . $rid, t('Role is removed from user.'));
+}
+
+
+}
