Index: /trunk/src/bp-groups/bp-groups-cache.php
===================================================================
--- /trunk/src/bp-groups/bp-groups-cache.php	(revision 10765)
+++ /trunk/src/bp-groups/bp-groups-cache.php	(revision 10766)
@@ -212,4 +212,18 @@
 }
 add_action( 'groups_member_after_save', 'groups_clear_group_administrator_cache_on_member_save' );
+
+/**
+ * Clear the group type cache for a group.
+ *
+ * Called when group is deleted.
+ *
+ * @since 2.6.0
+ *
+ * @param int $group_id The group ID.
+ */
+function groups_clear_group_type_cache( $group_id = 0 ) {
+	wp_cache_delete( $group_id, 'bp_groups_group_type' );
+}
+add_action( 'groups_delete_group', 'groups_clear_group_type_cache' );
 
 /* List actions to clear super cached pages on, if super cache is installed */
Index: /trunk/src/bp-groups/bp-groups-functions.php
===================================================================
--- /trunk/src/bp-groups/bp-groups-functions.php	(revision 10765)
+++ /trunk/src/bp-groups/bp-groups-functions.php	(revision 10766)
@@ -684,4 +684,5 @@
  *
  * @since 1.2.0
+ * @since 2.6.0 Added `$group_type`, `$group_type__in`, and `$group_type__not_in` parameters.
  *
  * @param array|string $args {
@@ -704,4 +705,7 @@
 		'exclude'           => false,          // Do not include these specific groups (group_ids).
 		'search_terms'      => false,          // Limit to groups that match these search terms.
+		'group_type'         => '',
+		'group_type__in'     => '',
+		'group_type__not_in' => '',
 		'meta_query'        => false,          // Filter by groupmeta. See WP_Meta_Query for syntax.
 		'show_hidden'       => false,          // Show hidden groups to non-admins.
@@ -720,4 +724,7 @@
 		'exclude'           => $r['exclude'],
 		'search_terms'      => $r['search_terms'],
+		'group_type'         => $r['group_type'],
+		'group_type__in'     => $r['group_type__in'],
+		'group_type__not_in' => $r['group_type__not_in'],
 		'meta_query'        => $r['meta_query'],
 		'show_hidden'       => $r['show_hidden'],
@@ -1851,2 +1858,289 @@
 add_action( 'delete_user',       'groups_remove_data_for_user' );
 add_action( 'bp_make_spam_user', 'groups_remove_data_for_user' );
+
+/** Group Types ***************************************************************/
+
+/**
+ * Register a group type.
+ *
+ * @since 2.6.0
+ *
+ * @param string $group_type Unique string identifier for the group type.
+ * @param array  $args {
+ *     Array of arguments describing the group type.
+ *
+ *     @type array $labels {
+ *         Array of labels to use in various parts of the interface.
+ *
+ *         @type string $name          Default name. Should typically be plural.
+ *         @type string $singular_name Singular name.
+ *     }
+ * }
+ * @return object|WP_Error Group type object on success, WP_Error object on failure.
+ */
+function bp_groups_register_group_type( $group_type, $args = array() ) {
+	$bp = buddypress();
+
+	if ( isset( $bp->groups->types[ $group_type ] ) ) {
+		return new WP_Error( 'bp_group_type_exists', __( 'Group type already exists.', 'buddypress' ), $group_type );
+	}
+
+	$r = bp_parse_args( $args, array(
+		'labels'        => array(),
+	), 'register_group_type' );
+
+	$group_type = sanitize_key( $group_type );
+
+	/**
+	 * Filters the list of illegal group type names.
+	 *
+	 * - 'any' is a special pseudo-type, representing items unassociated with any group type.
+	 * - 'null' is a special pseudo-type, representing users without any type.
+	 * - '_none' is used internally to denote an item that should not apply to any group types.
+	 *
+	 * @since 2.6.0
+	 *
+	 * @param array $illegal_names Array of illegal names.
+	 */
+	$illegal_names = apply_filters( 'bp_group_type_illegal_names', array( 'any', 'null', '_none' ) );
+	if ( in_array( $group_type, $illegal_names, true ) ) {
+		return new WP_Error( 'bp_group_type_illegal_name', __( 'You may not register a group type with this name.', 'buddypress' ), $group_type );
+	}
+
+	// Store the group type name as data in the object (not just as the array key).
+	$r['name'] = $group_type;
+
+	// Make sure the relevant labels have been filled in.
+	$default_name = isset( $r['labels']['name'] ) ? $r['labels']['name'] : ucfirst( $r['name'] );
+	$r['labels'] = array_merge( array(
+		'name'          => $default_name,
+		'singular_name' => $default_name,
+	), $r['labels'] );
+
+	$bp->groups->types[ $group_type ] = $type = (object) $r;
+
+	/**
+	 * Fires after a group type is registered.
+	 *
+	 * @since 2.6.0
+	 *
+	 * @param string $group_type Group type identifier.
+	 * @param object $type       Group type object.
+	 */
+	do_action( 'bp_groups_register_group_type', $group_type, $type );
+
+	return $type;
+}
+
+/**
+ * Get a list of all registered group type objects.
+ *
+ * @since 2.6.0
+ *
+ * @see bp_groups_register_group_type() for accepted arguments.
+ *
+ * @param array|string $args     Optional. An array of key => value arguments to match against
+ *                               the group type objects. Default empty array.
+ * @param string       $output   Optional. The type of output to return. Accepts 'names'
+ *                               or 'objects'. Default 'names'.
+ * @param string       $operator Optional. The logical operation to perform. 'or' means only one
+ *                               element from the array needs to match; 'and' means all elements
+ *                               must match. Accepts 'or' or 'and'. Default 'and'.
+ * @return array       $types    A list of groups type names or objects.
+ */
+function bp_groups_get_group_types( $args = array(), $output = 'names', $operator = 'and' ) {
+	$types = buddypress()->groups->types;
+
+	$types = wp_filter_object_list( $types, $args, $operator );
+
+	/**
+	 * Filters the array of group type objects.
+	 *
+	 * This filter is run before the $output filter has been applied, so that
+	 * filtering functions have access to the entire group type objects.
+	 *
+	 * @since 2.6.0
+	 *
+	 * @param array  $types     group type objects, keyed by name.
+	 * @param array  $args      Array of key=>value arguments for filtering.
+	 * @param string $operator  'or' to match any of $args, 'and' to require all.
+	 */
+	$types = apply_filters( 'bp_groups_get_group_types', $types, $args, $operator );
+
+	if ( 'names' === $output ) {
+		$types = wp_list_pluck( $types, 'name' );
+	}
+
+	return $types;
+}
+
+/**
+ * Retrieve a group type object by name.
+ *
+ * @since 2.6.0
+ *
+ * @param string $group_type The name of the group type.
+ * @return object A group type object.
+ */
+function bp_groups_get_group_type_object( $group_type ) {
+	$types = bp_groups_get_group_types( array(), 'objects' );
+
+	if ( empty( $types[ $group_type ] ) ) {
+		return null;
+	}
+
+	return $types[ $group_type ];
+}
+
+/**
+ * Set type for a group.
+ *
+ * @since 2.6.0
+ *
+ * @param int    $group      ID of the group.
+ * @param string $group_type Group type.
+ * @param bool   $append     Optional. True to append this to existing types for group,
+ *                           false to replace. Default: false.
+ * @return array $retval See bp_set_object_terms().
+ */
+function bp_groups_set_group_type( $group_id, $group_type, $append = false ) {
+	// Pass an empty group type to remove group's type.
+	if ( ! empty( $group_type ) && ! bp_groups_get_group_type_object( $group_type ) ) {
+		return false;
+	}
+
+	$retval = bp_set_object_terms( $group_id, $group_type, 'bp_group_type', $append );
+
+	// Bust the cache if the type has been updated.
+	if ( ! is_wp_error( $retval ) ) {
+		wp_cache_delete( $group_id, 'bp_groups_group_type' );
+
+		/**
+		 * Fires just after a group type has been changed.
+		 *
+		 * @since 2.6.0
+		 *
+		 * @param int    $group_id   ID of the group whose group type has been updated.
+		 * @param string $group_type Group type.
+		 * @param bool   $append     Whether the type is being appended to existing types.
+		 */
+		do_action( 'bp_groups_set_group_type', $group_id, $group_type, $append );
+	}
+
+	return $retval;
+}
+
+/**
+ * Get type for a group.
+ *
+ * @since 2.6.0
+ *
+ * @param int  $group_id ID of the group.
+ * @param bool $single   Optional. Whether to return a single type string. If multiple types are found
+ *                       for the group, the oldest one will be returned. Default: true.
+ * @return string|array|bool On success, returns a single group type (if `$single` is true) or an array of group
+ *                           types (if `$single` is false). Returns false on failure.
+ */
+function bp_groups_get_group_type( $group_id, $single = true ) {
+	$types = wp_cache_get( $group_id, 'bp_groups_group_type' );
+
+	if ( false === $types ) {
+		$types = bp_get_object_terms( $group_id, 'bp_group_type' );
+
+		if ( ! is_wp_error( $types ) ) {
+			$types = wp_list_pluck( $types, 'name' );
+			wp_cache_set( $group_id, $types, 'bp_groups_group_type' );
+		}
+	}
+
+	$type = false;
+	if ( ! empty( $types ) ) {
+		if ( $single ) {
+			$type = end( $types );
+		} else {
+			$type = $types;
+		}
+	}
+
+	/**
+	 * Filters a groups's group type(s).
+	 *
+	 * @since 2.6.0
+	 *
+	 * @param string|array $type     Group type.
+	 * @param int          $group_id ID of the group.
+	 * @param bool         $single   Whether to return a single type srting, or an array.
+	 */
+	return apply_filters( 'bp_groups_get_group_type', $type, $group_id, $single );
+}
+
+/**
+ * Remove type for a group.
+ *
+ * @since 2.6.0
+ *
+ * @param int            $group      ID of the user.
+ * @param string         $group_type Group type.
+ * @return bool|WP_Error $deleted    True on success. False or WP_Error on failure.
+ */
+function bp_groups_remove_group_type( $group_id, $group_type ) {
+	if ( empty( $group_type ) || ! bp_groups_get_group_type_object( $group_type ) ) {
+		return false;
+	}
+
+	$deleted = bp_remove_object_terms( $group_id, $group_type, 'bp_group_type' );
+
+	// Bust the case, if the type has been removed.
+	if ( ! is_wp_error( $deleted ) ) {
+		wp_cache_delete( $group_id, 'bp_groups_group_type' );
+
+		/**
+		 * Fires just after a group's group type has been removed.
+		 *
+		 * @since 2.6.0
+		 *
+		 * @param int    $group      ID of the group whose group type has been removed.
+		 * @param string $group_type Group type.
+		 */
+		do_action( 'bp_groups_remove_group_type', $group_id, $group_type );
+	}
+
+	return $deleted;
+}
+
+/**
+ * Check whether the given group has a certain group type.
+ *
+ * @since 2.6.0
+ *
+ * @param  int    $group_id   ID of the group.
+ * @param  srting $group_type Group type.
+ * @return bool   Whether the group has the give group type.
+ */
+function bp_groups_has_group_type( $group_id, $group_type ) {
+	if ( empty( $group_type ) || ! bp_groups_get_group_type_object( $group_type ) ) {
+		return false;
+	}
+
+	// Get all group's group types.
+	$types = bp_groups_get_group_type( $group_id, false );
+
+	if ( ! is_array( $types ) ) {
+		return false;
+	}
+
+	return in_array( $group_type, $types );
+}
+
+/**
+ * Delete a group's type when the group is deleted.
+ *
+ * @since 2.6.0
+ *
+ * @param  int   $group_id ID of the group.
+ * @return array $value    See {@see bp_groups_set_group_type()}.
+ */
+function bp_remove_group_type_on_group_delete( $group_id = 0 ) {
+	bp_groups_set_group_type( $group_id, '' );
+}
+add_action( 'groups_delete_group', 'bp_remove_group_type_on_group_delete' );
Index: /trunk/src/bp-groups/bp-groups-template.php
===================================================================
--- /trunk/src/bp-groups/bp-groups-template.php	(revision 10765)
+++ /trunk/src/bp-groups/bp-groups-template.php	(revision 10766)
@@ -103,4 +103,5 @@
  *
  * @since 1.0.0
+ * @since 2.6.0 Added `$group_type`, `$group_type__in`, and `$group_type__not_in` parameters.
  *
  * @param array|string $args {
@@ -140,4 +141,8 @@
  *                                           `$_REQUEST['groups_search']` or
  *                                           `$_REQUEST['s']`, if present. Otherwise false.
+ *     @type array|string $group_type         Array or comma-separated list of group types to limit results to.
+ *     @type array|string $group_type__in     Array or comma-separated list of group types to limit results to.
+ *     @type array|string $group_type__not_in Array or comma-separated list of group types that will be
+ *                                            excluded from results.
  *     @type array        $meta_query        An array of meta_query conditions.
  *                                           See {@link WP_Meta_Query::queries} for description.
@@ -207,4 +212,7 @@
 		'slug'              => $slug,
 		'search_terms'      => $search_terms,
+		'group_type'         => '',
+		'group_type__in'     => '',
+		'group_type__not_in' => '',
 		'meta_query'        => false,
 		'include'           => false,
@@ -227,4 +235,7 @@
 		'slug'              => $r['slug'],
 		'search_terms'      => $r['search_terms'],
+		'group_type'         => $r['group_type'],
+		'group_type__in'     => $r['group_type__in'],
+		'group_type__not_in' => $r['group_type__not_in'],
 		'meta_query'        => $r['meta_query'],
 		'include'           => $r['include'],
Index: /trunk/src/bp-groups/classes/class-bp-groups-component.php
===================================================================
--- /trunk/src/bp-groups/classes/class-bp-groups-component.php	(revision 10765)
+++ /trunk/src/bp-groups/classes/class-bp-groups-component.php	(revision 10766)
@@ -841,3 +841,15 @@
 		parent::setup_cache_groups();
 	}
+
+	/**
+	 * Set up taxonomies.
+	 *
+	 * @since 2.6.0
+	 */
+	public function register_taxonomies() {
+		// Group Type.
+		register_taxonomy( 'bp_group_type', 'bp_group', array(
+			'public' => false,
+		) );
+	}
 }
Index: /trunk/src/bp-groups/classes/class-bp-groups-group.php
===================================================================
--- /trunk/src/bp-groups/classes/class-bp-groups-group.php	(revision 10765)
+++ /trunk/src/bp-groups/classes/class-bp-groups-group.php	(revision 10766)
@@ -684,4 +684,5 @@
 	 *
 	 * @since 1.6.0
+	 * @since 2.6.0 Added `$group_type`, `$group_type__in`, and `$group_type__not_in` parameters.
 	 *
 	 * @param array $args {
@@ -705,4 +706,8 @@
 	 *                                           or descriptions match the search terms will be
 	 *                                           returned. Default: false.
+	 *     @type array|string $group_type         Array or comma-separated list of group types to limit results to.
+	 *     @type array|string $group_type__in     Array or comma-separated list of group types to limit results to.
+	 *     @type array|string $group_type__not_in Array or comma-separated list of group types that will be
+	 *                                            excluded from results.
 	 *     @type array        $meta_query        Optional. An array of meta_query conditions.
 	 *                                           See {@link WP_Meta_Query::queries} for description.
@@ -756,4 +761,7 @@
 			'user_id'           => 0,
 			'search_terms'      => false,
+			'group_type'         => '',
+			'group_type__in'     => '',
+			'group_type__not_in' => '',
 			'meta_query'        => false,
 			'include'           => false,
@@ -803,4 +811,22 @@
 		if ( ! empty( $meta_query_sql['where'] ) ) {
 			$sql['meta'] = $meta_query_sql['where'];
+		}
+
+		// Only use 'group_type__in', if 'group_type' is not set.
+		if ( empty( $r['group_type'] ) && ! empty( $r['group_type__in']) ) {
+			$r['group_type'] = $r['group_type__in'];
+		}
+
+		// Group types to exclude. This has priority over inclusions.
+		if ( ! empty( $r['group_type__not_in'] ) ) {
+			$group_type_clause = self::get_sql_clause_for_group_types( $r['group_type__not_in'], 'NOT IN' );
+
+		// Group types to include.
+		} elseif ( ! empty( $r['group_type'] ) ) {
+			$group_type_clause = self::get_sql_clause_for_group_types( $r['group_type'], 'IN' );
+		}
+
+		if ( ! empty( $group_type_clause ) ) {
+			$sql['group_type'] = $group_type_clause;
 		}
 
@@ -914,4 +940,9 @@
 			$meta_query_clause = preg_replace( '/^\s*AND/', '', $meta_query_sql['where'] );
 			$total_sql['where'][] = $meta_query_clause;
+		}
+
+		// Trim leading 'AND' to match `$total_sql` query style.
+		if ( ! empty( $group_type_clause ) ) {
+			$total_sql['where'][] = preg_replace( '/^\s*AND\s*/', '', $group_type_clause );
 		}
 
@@ -1625,3 +1656,71 @@
 		return $ids;
 	}
+
+	/**
+	 * Get SQL clause for group type(s).
+	 *
+	 * @since 2.6.0
+	 *
+	 * @param  string|array $group_types Group type(s).
+	 * @param  string       $operator    'IN' or 'NOT IN'.
+	 * @return string       $clause      SQL clause.
+	 */
+	protected static function get_sql_clause_for_group_types( $group_types, $operator ) {
+		global $wpdb;
+
+		// Sanitize operator.
+		if ( 'NOT IN' !== $operator ) {
+			$operator = 'IN';
+		}
+
+		// Parse and sanitize types.
+		if ( ! is_array( $group_types ) ) {
+			$group_types = preg_split( '/[,\s+]/', $group_types );
+		}
+
+		$types = array();
+		foreach ( $group_types as $gt ) {
+			if ( bp_groups_get_group_type_object( $gt ) ) {
+				$types[] = $gt;
+			}
+		}
+
+		$tax_query = new WP_Tax_Query( array(
+			array(
+				'taxonomy' => 'bp_group_type',
+				'field'    => 'name',
+				'operator' => $operator,
+				'terms'    => $types,
+			),
+		) );
+
+		$switched = false;
+		if ( ! bp_is_root_blog() ) {
+			switch_to_blog( bp_get_root_blog_id() );
+			$switched = true;
+		}
+
+		$sql_clauses = $tax_query->get_sql( 'g', 'id' );
+
+		if ( $switched ) {
+			restore_current_blog();
+		}
+
+		$clause = '';
+
+		// The no_results clauses are the same between IN and NOT IN.
+		if ( false !== strpos( $sql_clauses['where'], '0 = 1' ) ) {
+			$clause = $sql_clauses['where'];
+
+		// The tax_query clause generated for NOT IN can be used almost as-is.
+		} elseif ( 'NOT IN' === $operator ) {
+			$clause = $sql_clauses['where'];
+
+		// IN clauses must be converted to a subquery.
+		} elseif ( preg_match( '/' . $wpdb->term_relationships . '\.term_taxonomy_id IN \([0-9, ]+\)/', $sql_clauses['where'], $matches ) ) {
+			$clause = " AND g.id IN ( SELECT object_id FROM $wpdb->term_relationships WHERE {$matches[0]} )";
+		}
+
+		return $clause;
+	}
 }
Index: /trunk/src/bp-groups/classes/class-bp-groups-template.php
===================================================================
--- /trunk/src/bp-groups/classes/class-bp-groups-template.php	(revision 10765)
+++ /trunk/src/bp-groups/classes/class-bp-groups-template.php	(revision 10766)
@@ -167,4 +167,7 @@
 			'exclude'           => false,
 			'search_terms'      => '',
+			'group_type'         => '',
+			'group_type__in'     => '',
+			'group_type__not_in' => '',
 			'meta_query'        => false,
 			'populate_extras'   => true,
@@ -219,4 +222,7 @@
 				'search_terms'      => $search_terms,
 				'meta_query'        => $meta_query,
+				'group_type'         => $group_type,
+				'group_type__in'     => $group_type__in,
+				'group_type__not_in' => $group_type__not_in,
 				'include'           => $include,
 				'exclude'           => $exclude,
Index: /trunk/tests/phpunit/testcases/groups/class-bp-groups-group.php
===================================================================
--- /trunk/tests/phpunit/testcases/groups/class-bp-groups-group.php	(revision 10765)
+++ /trunk/tests/phpunit/testcases/groups/class-bp-groups-group.php	(revision 10766)
@@ -1030,4 +1030,343 @@
 		$this->set_current_user( $old_user );
 	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type_single_value() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type' => 'foo',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type_array_with_single_value() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type' => array( 'foo' ),
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type_with_comma_separated_list() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type' => 'foo, bar',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEqualSets( array( $g1, $g2 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type_array_with_multiple_values() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type' => array( 'foo', 'bar' ),
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEqualSets( array( $g1, $g2 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type_should_discart_non_existing_types_in_comma_separated_value() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type' => 'foo, baz',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type_should_return_empty_when_no_groups_match_specified_types() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type' => 'foo, baz',
+		) );
+
+		$this->assertEmpty( $groups['groups'] );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type__in_single_value() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type__in' => 'bar',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g2 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type__in_comma_separated_values() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type__in' => 'foo, bar',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEqualSets( array( $g1, $g2 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type__in_array_multiple_values() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type__in' => array( 'foo', 'bar' ),
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEqualSets( array( $g1, $g2 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type__in_array_with_single_value() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type__in' => array( 'foo' ),
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type__in_should_discart_non_existing_types_in_comma_separated_value() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type__in' => 'foo, baz',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type__in_should_return_empty_when_no_groups_match_specified_types() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type__in' => 'foo, baz',
+		) );
+
+		$this->assertEmpty( $groups['groups'] );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type_should_take_precedence_over_group_type__in() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type__in' => 'foo',
+			'group_type' => 'bar',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g2 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type__not_in_should_return_groups_with_types_and_without_types() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		$g3 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type__not_in' => 'foo',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g2, $g3 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type__not_in_comma_separated_values() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		$g3 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+		bp_groups_set_group_type( $g3, 'baz' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type__not_in' => 'foo, bar',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g3 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type__not_array_with_multiple_values() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		$g3 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'bar' );
+		bp_groups_set_group_type( $g3, 'baz' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type__not_in' => array( 'foo', 'bar' ),
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g3 ), $found );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type__not_in_should_return_no_results_when_all_groups_mathc_sepecified_type() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		$g3 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'foo' );
+		bp_groups_set_group_type( $g3, 'foo' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type__not_in' => 'foo',
+		) );
+
+		$this->assertEmpty( $groups['groups'] );
+	}
+
+	/**
+	 * @group group_types
+	 */
+	public function test_group_type__not_in_takes_precedence_over_group_type() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		$g3 = $this->factory->group->create();
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_set_group_type( $g1, 'foo' );
+		bp_groups_set_group_type( $g2, 'foo' );
+		bp_groups_set_group_type( $g3, 'foo' );
+
+		$groups = BP_Groups_Group::get( array(
+			'group_type' => 'foo',
+			'group_type__not_in' => 'foo',
+		) );
+
+		$this->assertEmpty( $groups['groups'] );
+	}
 }
 
Index: /trunk/tests/phpunit/testcases/groups/types.php
===================================================================
--- /trunk/tests/phpunit/testcases/groups/types.php	(revision 10766)
+++ /trunk/tests/phpunit/testcases/groups/types.php	(revision 10766)
@@ -0,0 +1,253 @@
+<?php
+
+/**
+ * @group groups
+ * @group group_types
+ */
+class BP_Tests_Groups_Types extends BP_UnitTestCase {
+	protected static $u1 = null;
+
+	public function setUp() {
+		parent::setUp();
+
+		buddypress()->groups->types = array();
+
+		self::$u1 = $this->factory->user->create();
+	}
+
+	public static function setUpBeforeClass() {
+		$f = new BP_UnitTest_Factory();
+
+		self::$u1 = $f->user->create();
+	}
+
+	public static function tearDownAfterClass() {
+		if ( is_multisite() ) {
+			wpmu_delete_user( self::$u1 );
+		} else {
+			wp_delete_user( self::$u1 );
+		}
+	}
+
+	public function test_groups_register_type_should_fail_for_existing_group_type() {
+		bp_groups_register_group_type( 'foo' );
+		$this->assertWPError( bp_groups_register_group_type( 'foo' ) );
+	}
+
+	/**
+	 * @dataProvider illegal_names
+	 */
+	public function test_illegal_names( $name ) {
+		$this->assertWPError( bp_groups_register_group_type( $name ) );
+	}
+
+	public function illegal_names() {
+		return array(
+			array( 'any' ),
+			array( 'null' ),
+			array( '_none' ),
+		);
+	}
+
+	public function test_groups_register_type_should_sanitize_group_type_key() {
+		$key = 'F//oo% -Bar';
+		$sanitized_key = 'foo-bar';
+
+		$object = bp_groups_register_group_type( $key );
+		$this->assertSame( $sanitized_key, $object->name );
+	}
+
+	public function test_groups_register_type_should_store_group_type_string_as_name_property() {
+		$object = bp_groups_register_group_type( 'foo' );
+		$this->assertSame( 'foo', $object->name );
+	}
+
+	public function test_groups_register_type_should_fill_missing_labels_with_ucfirst_group_type() {
+		$object = bp_groups_register_group_type( 'foo' );
+		foreach ( $object->labels as $label ) {
+			$this->assertSame( 'Foo', $label );
+		}
+	}
+
+	public function test_groups_register_type_should_respect_custom_name_labels() {
+		$object = bp_groups_register_group_type( 'foo', array(
+			'labels' => array(
+				'name' => 'Bar',
+			),
+		) );
+
+		$this->assertSame( 'Bar', $object->labels['name'] );
+		$this->assertSame( 'Bar', $object->labels['singular_name'] );
+	}
+
+	public function test_groups_register_type_should_respect_custom_singular_name_labels() {
+		$object = bp_groups_register_group_type( 'foo', array(
+			'labels' => array(
+				'singular_name' => 'Bar',
+			),
+		) );
+
+		$this->assertSame( 'Foo', $object->labels['name'] );
+		$this->assertSame( 'Bar', $object->labels['singular_name'] );
+	}
+
+	public function test_groups_get_type_object_should_return_null_for_non_existing_group_type() {
+		$this->assertSame( null, bp_groups_get_group_type_object( 'foo' ) );
+	}
+
+	public function test_groups_get_type_object_should_return_type_object() {
+		bp_groups_register_group_type( 'foo' );
+		$this->assertInternalType( 'object', bp_groups_register_group_type( 'foo' ) );
+	}
+
+	public function test_groups_set_type_should_return_false_for_invalid_group_type() {
+		$this->assertFalse( bp_groups_set_group_type( 1, 'foo' ) );
+	}
+
+	public function test_groups_set_type_success() {
+		$g = $this->factory->group->create( array( 'creator_id' => self::$u1 ) );
+		bp_groups_register_group_type( 'foo' );
+
+		$this->assertNotEmpty( bp_groups_set_group_type( $g, 'foo' ) );
+	}
+
+	public function test_groups_set_type_should_remove_type_when_passing_an_empty_value() {
+		$g = $this->factory->group->create( array( 'creator_id' => self::$u1 ) );
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_set_group_type( $g, 'foo' );
+
+		// Make sure group type is set.
+		$this->assertSame( 'foo', bp_groups_get_group_type( $g ) );
+
+		$this->assertSame( array(), bp_groups_set_group_type( $g, '' ) );
+		$this->assertFalse( bp_groups_get_group_type( $g ) );
+	}
+
+	public function test_groups_get_type_with_default_value_for_single() {
+		$g = $this->factory->group->create( array( 'creator_id' => self::$u1 ) );
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g, 'foo' );
+		bp_groups_set_group_type( $g, 'bar', true );
+
+		$this->assertSame( 'foo', bp_groups_get_group_type( $g ) );
+	}
+
+	public function test_groups_get_type_with_single_true() {
+		$g = $this->factory->group->create( array( 'creator_id' => self::$u1 ) );
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g, 'foo' );
+		bp_groups_set_group_type( $g, 'bar', true );
+
+		$this->assertSame( 'foo', bp_groups_get_group_type( $g, true ) );
+	}
+
+	public function test_groups_get_type_with_single_false() {
+		$g = $this->factory->group->create( array( 'creator_id' => self::$u1 ) );
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g, 'foo' );
+		bp_groups_set_group_type( $g, 'bar', true );
+
+		$this->assertEqualSets( array( 'foo', 'bar' ), bp_groups_get_group_type( $g, false ) );
+	}
+
+	public function test_groups_get_type_should_return_false_when_no_value_is_found() {
+		$g = $this->factory->group->create( array( 'creator_id' => self::$u1 ) );
+		bp_groups_register_group_type( 'foo' );
+
+		$this->assertFalse( bp_groups_get_group_type( $g ) );
+	}
+
+	public function test_groups_remove_type_should_return_false_when_group_type_is_empty() {
+		$this->assertFalse( bp_groups_remove_group_type( 9, '' ) );
+	}
+
+	public function test_groups_remove_type_should_return_false_when_group_type_is_invalid() {
+		$this->assertFalse( bp_groups_remove_group_type( 9, 'foo' ) );
+	}
+
+	public function test_groups_remove_type_should_return_false_when_group_is_not_of_provided_type() {
+		$g = $this->factory->group->create( array( 'creator_id' => self::$u1 ) );
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g, 'foo' );
+
+		$this->assertFalse( bp_groups_remove_group_type( $g, 'bar' ) );
+		$this->assertEquals( array( 'foo' ), bp_groups_get_group_type( $g, false ) );
+	}
+
+	public function tests_groups_remove_type_should_return_true_on_successful_deletion() {
+		$g = $this->factory->group->create( array( 'creator_id' => self::$u1 ) );
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g, 'foo' );
+		bp_groups_set_group_type( $g, 'bar', true );
+
+		$this->assertTrue( bp_groups_remove_group_type( $g, 'foo' ) );
+		$this->assertEquals( array( 'bar' ), bp_groups_get_group_type( $g, false ) );
+	}
+
+	public function test_groups_has_type_should_return_false_when_group_type_is_empty() {
+		$this->assertFalse( bp_groups_has_group_type( 9, '' ) );
+	}
+
+	public function test_groups_has_type_should_return_false_when_group_type_is_invalid() {
+		$this->assertFalse( bp_groups_has_group_type( 9, 'foo' ) );
+	}
+
+	public function test_groups_has_type_should_return_false_when_group_id_is_empty() {
+		bp_groups_register_group_type( 'foo' );
+
+		$this->assertFalse( bp_groups_has_group_type( '', 'foo' ) );
+	}
+
+	public function test_groups_has_type_should_return_false_when_group_is_not_of_provided_type() {
+		$g = $this->factory->group->create( array( 'creator_id' => self::$u1 ) );
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g, 'foo' );
+
+		$this->assertFalse( bp_groups_has_group_type( $g, 'bar' ) );
+	}
+
+	public function test_groups_has_type_should_return_true_on_success() {
+		$g = $this->factory->group->create( array( 'creator_id' => self::$u1 ) );
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+		bp_groups_set_group_type( $g, 'foo' );
+		bp_groups_set_group_type( $g, 'bar', true );
+
+		$this->assertTrue( bp_groups_has_group_type( $g, 'foo' ) );
+		$this->assertEqualSets( array( 'bar', 'foo' ), bp_groups_get_group_type( $g, false ) );
+	}
+
+	/**
+	 * @group cache
+	 */
+	public function test_groups_get_type_should_hit_cache() {
+		$g = $this->factory->group->create( array( 'creator_id' => self::$u1 ) );
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_set_group_type( $g, 'foo' );
+
+		global $wpdb;
+
+		// Initial query. Should hit DB.
+		bp_groups_get_group_type( $g );
+		$num_queries = $wpdb->num_queries;
+
+		// Next query should hit cache
+		bp_groups_get_group_type( $g );
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+	}
+
+	public function test_bp_groups_get_group_types_filter() {
+		bp_groups_register_group_type( 'foo' );
+		bp_groups_register_group_type( 'bar' );
+
+		$types = bp_groups_get_group_types( array( 'name' => 'bar' ) );
+
+		$this->assertEqualSets( array( 'bar' ), $types );
+	}
+}
