Fri, 17 Feb 2023 19:32:38 -0600
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(); }