libpurple/tests/test_person.c

Fri, 17 Feb 2023 19:32:38 -0600

author
Gary Kramlich <grim@reaperworld.com>
date
Fri, 17 Feb 2023 19:32:38 -0600
changeset 42059
e6dcbf0db616
parent 42054
7aa77854392d
child 42184
4e1bf25f5575
permissions
-rw-r--r--

Add search support to the contact list

This searches everything in `PurplePerson` and all of it's contacts, but does not yet cover tags. I am planning on covering that in another review request.

Right now the matching is just using `strstr`, but will will update this as part of [PIDGIN-17737](https://issues.imfreedom.org/issue/PIDGIN-17737/).

I also tried to figure out how to get focus back to the list view, but didn't come up with any viable solutions.

Testing Done:
Ran the unit tests and did a bunch of searches in the contact list with the demo protocol plugin.

Bugs closed: PIDGIN-17717

Reviewed at https://reviews.imfreedom.org/r/2211/

/*
 * Purple - Internet Messaging Library
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib.h>

#include <purple.h>

#include "test_ui.h"

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
test_purple_person_items_changed_cb(G_GNUC_UNUSED GListModel *model,
                                    G_GNUC_UNUSED guint position,
                                    G_GNUC_UNUSED guint removed,
                                    G_GNUC_UNUSED guint added,
                                    gpointer data)
{
	gboolean *called = data;

	*called = TRUE;
}

static void
test_purple_person_notify_cb(G_GNUC_UNUSED GObject *obj,
                             G_GNUC_UNUSED GParamSpec *pspec,
                             gpointer data)
{
	gboolean *called = data;

	*called = TRUE;
}

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_purple_person_new(void) {
	PurplePerson *person = NULL;

	person = purple_person_new();

	g_assert_true(PURPLE_IS_PERSON(person));

	g_clear_object(&person);
}

static void
test_purple_person_properties(void) {
	PurpleContact *person = NULL;
	PurpleTags *tags = NULL;
	GdkPixbuf *avatar = NULL;
	GdkPixbuf *avatar1 = NULL;
	GdkPixbuf *avatar_for_display = NULL;
	char *id = NULL;
	char *alias = NULL;
	char *name_for_display = NULL;

	/* Create our avatar for testing. */
	avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);

	/* Use g_object_new so we can test setting properties by name. All of them
	 * call the setter methods, so by doing it this way we exercise more of the
	 * code.
	 */
	person = g_object_new(
		PURPLE_TYPE_PERSON,
		"alias", "alias",
		"avatar", avatar,
		NULL);

	/* Now use g_object_get to read all of the properties. */
	g_object_get(person,
		"id", &id,
		"alias", &alias,
		"avatar", &avatar1,
		"avatar-for-display", &avatar_for_display,
		"name-for-display", &name_for_display,
		"tags", &tags,
		NULL);

	/* Compare all the things. */
	g_assert_nonnull(id);
	g_assert_cmpstr(alias, ==, "alias");
	g_assert_true(avatar1 == avatar);
	g_assert_true(avatar1 == avatar_for_display);
	g_assert_cmpstr(name_for_display, ==, "alias");
	g_assert_nonnull(tags);

	/* Free/unref all the things. */
	g_clear_pointer(&id, g_free);
	g_clear_pointer(&alias, g_free);
	g_clear_object(&avatar1);
	g_clear_object(&avatar_for_display);
	g_clear_pointer(&name_for_display, g_free);
	g_clear_object(&tags);

	g_clear_object(&avatar);
	g_clear_object(&person);
}

static void
test_purple_person_avatar_for_display_person(void) {
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;
	GdkPixbuf *avatar = NULL;

	person = purple_person_new();
	avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
	purple_person_set_avatar(person, avatar);

	info = purple_contact_info_new("id");
	purple_person_add_contact_info(person, info);

	/* Make sure the person's alias is overriding the contact info. */
	g_assert_true(purple_person_get_avatar_for_display(person) == avatar);

	g_clear_object(&info);
	g_clear_object(&person);
	g_clear_object(&avatar);
}

static void
test_purple_person_avatar_for_display_contact(void) {
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;
	GdkPixbuf *avatar = NULL;

	person = purple_person_new();

	info = purple_contact_info_new("id");
	avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
	purple_contact_info_set_avatar(info, avatar);
	purple_person_add_contact_info(person, info);

	/* Make sure the person's alias is overriding the contact info. */
	g_assert_true(purple_person_get_avatar_for_display(person) == avatar);

	g_clear_object(&info);
	g_clear_object(&person);
	g_clear_object(&avatar);
}

static void
test_purple_person_name_for_display_person(void) {
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;

	person = purple_person_new();
	purple_person_set_alias(person, "person-alias");

	info = purple_contact_info_new("id");
	purple_person_add_contact_info(person, info);

	/* Make sure the person's alias is overriding the contact info. */
	g_assert_cmpstr(purple_person_get_name_for_display(person), ==,
	                "person-alias");

	g_clear_object(&info);
	g_clear_object(&person);
}

static void
test_purple_person_name_for_display_contact(void) {
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;

	person = purple_person_new();

	info = purple_contact_info_new("id");
	purple_person_add_contact_info(person, info);

	/* Make sure the contact info's name for display is called when the
	 * person's alias is unset.
	 */
	g_assert_cmpstr(purple_person_get_name_for_display(person), ==, "id");

	g_clear_object(&info);
	g_clear_object(&person);
}

static void
test_purple_person_contacts_single(void) {
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;
	PurplePerson *person1 = NULL;
	guint n_items = 0;
	gboolean removed = FALSE;
	gboolean changed = FALSE;

	info = purple_contact_info_new("id");
	person = purple_person_new();
	g_signal_connect(person, "items-changed",
	                 G_CALLBACK(test_purple_person_items_changed_cb), &changed);

	n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
	g_assert_cmpuint(n_items, ==, 0);
	purple_person_add_contact_info(person, info);
	n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
	g_assert_cmpuint(n_items, ==, 1);
	g_assert_true(changed);

	person1 = purple_contact_info_get_person(info);
	g_assert_true(person1 == person);

	changed = FALSE;

	removed = purple_person_remove_contact_info(person, info);
	g_assert_true(removed);
	n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
	g_assert_cmpuint(n_items, ==, 0);
	g_assert_true(changed);

	person1 = purple_contact_info_get_person(info);
	g_assert_null(person1);

	g_clear_object(&person);
	g_clear_object(&info);
}

static void
test_purple_person_contacts_multiple(void) {
	PurplePerson *person = NULL;
	GPtrArray *infos = NULL;
	guint n_items = 0;
	const gint n_infos = 5;
	gboolean changed = FALSE;

	person = purple_person_new();
	g_signal_connect(person, "items-changed",
	                 G_CALLBACK(test_purple_person_items_changed_cb), &changed);

	infos = g_ptr_array_new_full(n_infos, g_object_unref);
	for(gint i = 0; i < n_infos; i++) {
		PurpleContactInfo *info = NULL;
		gchar *username = NULL;

		changed = FALSE;

		n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
		g_assert_cmpuint(n_items, ==, i);

		username = g_strdup_printf("username%d", i);
		info = purple_contact_info_new(NULL);
		purple_contact_info_set_username(info, username);
		g_free(username);

		/* Add the contact info to the ptr array so we can remove it below. */
		g_ptr_array_add(infos, info);

		/* Add the contact info to the person and make sure that all the magic
		 * happened.
		 */
		purple_person_add_contact_info(person, info);
		n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
		g_assert_cmpuint(n_items, ==, i + 1);
		g_assert_true(changed);
	}

	for(gint i = 0; i < n_infos; i++) {
		PurpleContactInfo *info = g_ptr_array_index(infos, i);
		gboolean removed = FALSE;

		changed = FALSE;

		n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
		g_assert_cmpuint(n_items, ==, n_infos - i);

		removed = purple_person_remove_contact_info(person, info);
		g_assert_true(removed);

		n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
		g_assert_cmpuint(n_items, ==, n_infos - (i + 1));

		g_assert_true(changed);
	}

	/* Final sanity check that the person has no more contacts. */
	n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
	g_assert_cmpuint(n_items, ==, 0);

	g_ptr_array_free(infos, TRUE);

	g_clear_object(&person);
}

static void
test_purple_person_priority_single(void) {
	PurpleContactInfo *info = NULL;
	PurpleContactInfo *priority = NULL;
	PurplePerson *person = NULL;
	PurplePresence *presence = NULL;
	PurpleStatus *status = NULL;
	PurpleStatusType *status_type = NULL;
	gboolean called = FALSE;

	person = purple_person_new();
	g_signal_connect(person, "notify::priority-contact-info",
	                 G_CALLBACK(test_purple_person_notify_cb), &called);
	priority = purple_person_get_priority_contact_info(person);
	g_assert_null(priority);

	/* Now create a real contact. */
	info = purple_contact_info_new(NULL);
	purple_person_add_contact_info(person, info);

	/* Set the status of the contact. */
	presence = purple_contact_info_get_presence(info);
	status_type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, "available",
	                                     "Available", FALSE);
	status = purple_status_new(status_type, presence);
	g_object_set(G_OBJECT(presence), "active-status", status, NULL);
	g_clear_object(&status);

	g_assert_true(called);

	priority = purple_person_get_priority_contact_info(person);
	g_assert_true(priority == info);

	purple_status_type_destroy(status_type);
	g_clear_object(&person);
	g_clear_object(&info);
	g_clear_object(&presence);
}

static void
test_purple_person_priority_multiple_with_change(void) {
	PurpleContactInfo *priority = NULL;
	PurpleContactInfo *first = NULL;
	PurpleContactInfo *sorted_contact = NULL;
	PurplePerson *person = NULL;
	PurplePresence *sorted_presence = NULL;
	PurpleStatus *status = NULL;
	PurpleStatusType *available = NULL;
	PurpleStatusType *offline = NULL;
	gboolean changed = FALSE;
	gint n_infos = 5;
	guint n_items = 0;

	/* This unit test is a bit complicated, but it adds 5 contact infos to a
	 * person all whose presences are set to offline. After adding all the
	 * contact infos, we verify that the first contact info we added is the
	 * priority contact info. Then we flip the active status of the n_infos - 2
	 * infos to available. This should make it the priority contact info which
	 * we then assert.
	 */

	/* Create our status types. */
	available = purple_status_type_new(PURPLE_STATUS_AVAILABLE, "available",
	                                   "Available", FALSE);
	offline = purple_status_type_new(PURPLE_STATUS_OFFLINE, "offline",
	                                 "Offline", FALSE);

	/* Create the person and connected to the notify signal for the
	 * priority-contact property.
	 */
	person = purple_person_new();
	g_signal_connect(person, "notify::priority-contact-info",
	                 G_CALLBACK(test_purple_person_notify_cb), &changed);
	priority = purple_person_get_priority_contact_info(person);
	g_assert_null(priority);

	/* Create and add all contact infos. */
	for(gint i = 0; i < n_infos; i++) {
		PurpleContactInfo *info = NULL;
		PurplePresence *presence = NULL;
		gchar *username = NULL;

		/* Set changed to false as it shouldn't be changed. */
		changed = FALSE;

		/* Now create a real contact. */
		username = g_strdup_printf("username%d", i + 1);
		info = purple_contact_info_new(NULL);
		purple_contact_info_set_username(info, username);
		g_free(username);

		/* Set the status for the contact. */
		presence = purple_contact_info_get_presence(info);
		status = purple_status_new(offline, presence);
		g_object_set(G_OBJECT(presence), "active-status", status, NULL);
		g_clear_object(&status);

		purple_person_add_contact_info(person, info);

		if(i == 0) {
			first = g_object_ref(info);
			g_assert_true(changed);
		} else {
			g_assert_false(changed);

			if(i == n_infos - 2) {
				sorted_contact = g_object_ref(info);
				sorted_presence = g_object_ref(presence);
			}
		}

		g_clear_object(&info);
	}

	n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
	g_assert_cmpuint(n_items, ==, n_infos);

	priority = purple_person_get_priority_contact_info(person);
	g_assert_true(priority == first);
	g_clear_object(&first);

	/* Now set the second from the last contact info's status to available, and
	 * verify that that contact info is now the priority contact info.
	 */
	changed = FALSE;
	status = purple_status_new(available, sorted_presence);
	g_object_set(G_OBJECT(sorted_presence), "active-status", status, NULL);
	g_clear_object(&status);
	g_assert_true(changed);
	priority = purple_person_get_priority_contact_info(person);
	g_assert_true(priority == sorted_contact);

	/* Cleanup. */
	purple_status_type_destroy(offline);
	purple_status_type_destroy(available);

	g_clear_object(&sorted_contact);
	g_clear_object(&sorted_presence);

	g_clear_object(&person);
}

/******************************************************************************
 * Matches tests
 *****************************************************************************/
static void
test_purple_person_matches_accepts_null(void) {
	PurplePerson *person = purple_person_new();

	g_assert_true(purple_person_matches(person, NULL));

	g_clear_object(&person);
}

static void
test_purple_person_matches_empty_string(void) {
	PurplePerson *person = purple_person_new();

	g_assert_true(purple_person_matches(person, ""));

	g_clear_object(&person);
}

static void
test_purple_person_matches_alias(void) {
	PurplePerson *person = purple_person_new();

	purple_person_set_alias(person, "this is the alias");

	g_assert_true(purple_person_matches(person, "the"));
	g_assert_false(purple_person_matches(person, "what"));

	g_clear_object(&person);
}

static void
test_purple_person_matches_contact_info(void) {
	PurplePerson *person = purple_person_new();
	PurpleContactInfo *info = purple_contact_info_new(NULL);

	purple_contact_info_set_username(info, "user1");
	purple_person_add_contact_info(person, info);
	g_clear_object(&info);

	g_assert_true(purple_person_matches(person, "user1"));

	g_clear_object(&person);
}

/******************************************************************************
 * Main
 *****************************************************************************/
gint
main(gint argc, gchar *argv[]) {
	g_test_init(&argc, &argv, NULL);

	test_ui_purple_init();

	g_test_add_func("/person/new",
	                test_purple_person_new);
	g_test_add_func("/person/properties",
	                test_purple_person_properties);

	g_test_add_func("/person/avatar-for-display/person",
	                test_purple_person_avatar_for_display_person);
	g_test_add_func("/person/avatar-for-display/contact",
	                test_purple_person_avatar_for_display_contact);

	g_test_add_func("/person/name-for-display/person",
	                test_purple_person_name_for_display_person);
	g_test_add_func("/person/name-for-display/contact",
	                test_purple_person_name_for_display_contact);

	g_test_add_func("/person/contacts/single",
	                test_purple_person_contacts_single);
	g_test_add_func("/person/contacts/multiple",
	                test_purple_person_contacts_multiple);

	g_test_add_func("/person/priority/single",
	                test_purple_person_priority_single);
	g_test_add_func("/person/priority/multiple-with-change",
	                test_purple_person_priority_multiple_with_change);

	g_test_add_func("/person/matches/accepts_null",
	                test_purple_person_matches_accepts_null);
	g_test_add_func("/person/matches/empty_string",
	                test_purple_person_matches_empty_string);
	g_test_add_func("/person/matches/alias",
	                test_purple_person_matches_alias);
	g_test_add_func("/person/matches/contact_info",
	                test_purple_person_matches_contact_info);

	return g_test_run();
}

mercurial