/*
 *  $Id: inventory.c 28076 2025-06-09 14:55:16Z yeti-dn $
 *  Copyright (C) 2009-2025 David Nečas (Yeti).
 *  E-mail: yeti@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program 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 General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "tests/testlibgwy.h"

/* Cannot make it static because we get it non-static automatically from the macro. */
GType gwy_item_test_get_type(void) G_GNUC_CONST;

#define GWY_TYPE_ITEM_TEST           (gwy_item_test_get_type())
#define GWY_ITEM_TEST(obj)           (G_TYPE_CHECK_INSTANCE_CAST((obj), GWY_TYPE_ITEM_TEST, GwyItemTest))
#define GWY_IS_ITEM_TEST(obj)        (G_TYPE_CHECK_INSTANCE_TYPE((obj), GWY_TYPE_ITEM_TEST))
#define GWY_ITEM_TEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GWY_TYPE_ITEM_TEST, GwyItemTestClass))

typedef struct {
    GObject g_object;
    gchar *name;
    gint value;
} GwyItemTest;

typedef struct {
    GObjectClass gobject_class;
} GwyItemTestClass;

static int item_destroy_count;
static gboolean sort_by_name;

G_DEFINE_TYPE(GwyItemTest, gwy_item_test, G_TYPE_OBJECT);

static void
gwy_item_test_init(G_GNUC_UNUSED GwyItemTest *item)
{
}

static void
gwy_item_test_class_init(G_GNUC_UNUSED GwyItemTestClass *klass)
{
}

static GObject*
item_new(const gchar *name, gint value)
{
    GwyItemTest *itemtest = g_object_newv(GWY_TYPE_ITEM_TEST, 0, NULL);
    itemtest->name = g_strdup(name);
    itemtest->value = value;
    return (GObject*)itemtest;
}

static const gchar*
item_get_name(gpointer item)
{
    const GwyItemTest *itemtest = (const GwyItemTest*)item;
    return itemtest->name;
}

static gboolean
item_is_const(gconstpointer item)
{
    const GwyItemTest *itemtest = (const GwyItemTest*)item;
    return itemtest->value < 0;
}

static gint
item_compare(gconstpointer a,
             gconstpointer b)
{
    const GwyItemTest *itemtesta = (const GwyItemTest*)a;
    const GwyItemTest *itemtestb = (const GwyItemTest*)b;
    return strcmp(itemtesta->name, itemtestb->name);
}

static gint
item_compare_tricky(gconstpointer a,
                    gconstpointer b)
{
    if (sort_by_name)
        return item_compare(a, b);

    const GwyItemTest *itemtesta = (const GwyItemTest*)a;
    const GwyItemTest *itemtestb = (const GwyItemTest*)b;
    return itemtesta->value - itemtestb->value;
}

static void
item_rename(gpointer item,
            const gchar *newname)
{
    GwyItemTest *itemtest = (GwyItemTest*)item;
    g_free(itemtest->name);
    itemtest->name = g_strdup(newname);
}

static void
item_destroy(gpointer item)
{
    GwyItemTest *itemtest = (GwyItemTest*)item;
    g_free(itemtest->name);
    g_object_unref(item);
    item_destroy_count++;
}

static gpointer
item_copy(gpointer item)
{
    const GwyItemTest *itemtest = (const GwyItemTest*)item;
    GwyItemTest *copy = g_object_newv(GWY_TYPE_ITEM_TEST, 0, NULL);
    copy->value = itemtest->value;
    copy->name = g_strdup(itemtest->name);
    return (GObject*)copy;
}

void
record_item_change(G_GNUC_UNUSED GObject *object,
                   guint pos,
                   guint64 *counter)
{
    *counter <<= 4;
    *counter |= pos + 1;
}

static GwyInventory*
make_simple_inventory(void)
{
    GwyInventoryItemType item_type = {
        0,
        NULL,
        item_is_const,
        item_get_name,
        item_compare,
        item_rename,
        item_destroy,
        item_copy,
        NULL,
        NULL,
        NULL,
    };

    GwyInventory *inventory = gwy_inventory_new(&item_type);
    g_assert_true(GWY_IS_INVENTORY(inventory));
    return inventory;
}

void
test_inventory_data_name(void)
{
    GwyInventory *inventory = make_simple_inventory();
    item_destroy_count = 0;

    guint64 insert_log = 0, update_log = 0, delete_log = 0;
    g_signal_connect(inventory, "item-inserted", G_CALLBACK(record_item_change), &insert_log);
    g_signal_connect(inventory, "item-updated", G_CALLBACK(record_item_change), &update_log);
    g_signal_connect(inventory, "item-deleted", G_CALLBACK(record_item_change), &delete_log);

    gwy_inventory_insert_item(inventory, item_new("Fixme", -1));
    gwy_inventory_insert_item(inventory, item_new("Second", 2));
    gwy_inventory_insert_item(inventory, item_new("First", 1));
    g_assert_cmpuint(gwy_inventory_get_n_items(inventory), ==, 3);
    g_assert_cmphex(insert_log, ==, 0x121);
    g_assert_cmphex(update_log, ==, 0);
    g_assert_cmphex(delete_log, ==, 0);
    insert_log = 0;

    gwy_inventory_item_updated(inventory, "Second");
    gwy_inventory_item_updated(inventory, "First");
    gwy_inventory_item_updated(inventory, "Second");
    g_assert_cmpuint(gwy_inventory_get_n_items(inventory), ==, 3);
    g_assert_cmphex(insert_log, ==, 0);
    // Inventory is sorted so "Second" comes after "Fixme".
    g_assert_cmphex(update_log, ==, 0x313);
    g_assert_cmphex(delete_log, ==, 0);
    update_log = 0;

    gwy_inventory_nth_item_updated(inventory, 0);
    gwy_inventory_nth_item_updated(inventory, 1);
    gwy_inventory_nth_item_updated(inventory, 2);
    g_assert_cmpuint(gwy_inventory_get_n_items(inventory), ==, 3);
    g_assert_cmphex(insert_log, ==, 0);
    g_assert_cmphex(update_log, ==, 0x123);
    g_assert_cmphex(delete_log, ==, 0);
    update_log = 0;
    g_object_unref(inventory);
    // FIXME: Tradidionally, Inventory has not destroyed items when itself was destroyed. Only old Gwyddion 3 does it.
    // It may be a sensible behaviour but we do not actually expect it now. */
    //g_assert_cmpuint(item_destroy_count, ==, 3);
    g_assert_cmpuint(item_destroy_count, ==, 0);
}

void
test_inventory_data_nth(void)
{
    GwyInventory *inventory = make_simple_inventory();
    item_destroy_count = 0;

    GwyItemTest *itemtest;
    guint64 insert_log = 0, update_log = 0, delete_log = 0;
    g_signal_connect(inventory, "item-inserted", G_CALLBACK(record_item_change), &insert_log);
    g_signal_connect(inventory, "item-updated", G_CALLBACK(record_item_change), &update_log);
    g_signal_connect(inventory, "item-deleted", G_CALLBACK(record_item_change), &delete_log);

    gwy_inventory_insert_nth_item(inventory, item_new("Fixme", -1), 0);
    gwy_inventory_insert_nth_item(inventory, item_new("Second", 2), 1);
    gwy_inventory_insert_nth_item(inventory, item_new("First", 1), 0);
    g_assert_cmpuint(gwy_inventory_get_n_items(inventory), ==, 3);
    g_assert_cmphex(insert_log, ==, 0x121);
    g_assert_cmphex(update_log, ==, 0);
    g_assert_cmphex(delete_log, ==, 0);
    insert_log = 0;

    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_item(inventory, "Fixme")));
    g_assert_cmpint(itemtest->value, ==, -1);
    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_item(inventory, "Second")));
    g_assert_cmpint(itemtest->value, ==, 2);
    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_item(inventory, "First")));
    g_assert_cmpint(itemtest->value, ==, 1);

    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_nth_item(inventory, 0)));
    g_assert_cmpstr(itemtest->name, ==, "First");
    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_nth_item(inventory, 1)));
    g_assert_cmpstr(itemtest->name, ==, "Fixme");
    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_nth_item(inventory, 2)));
    g_assert_cmpstr(itemtest->name, ==, "Second");

    gwy_inventory_forget_order(inventory);
    g_assert_cmphex(insert_log, ==, 0);
    g_assert_cmphex(update_log, ==, 0);
    g_assert_cmphex(delete_log, ==, 0);

    gwy_inventory_insert_item(inventory, item_new("Abel", 0));
    gwy_inventory_insert_item(inventory, item_new("Kain", 1));
    g_assert_cmphex(insert_log, ==, 0x45);
    g_assert_cmphex(update_log, ==, 0);
    g_assert_cmphex(delete_log, ==, 0);
    insert_log = 0;
    gwy_inventory_restore_order(inventory);
    g_assert_cmpuint(gwy_inventory_get_n_items(inventory), ==, 5);
    g_assert_cmphex(insert_log, ==, 0);
    g_assert_cmphex(update_log, ==, 0);
    g_assert_cmphex(delete_log, ==, 0);

    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_nth_item(inventory, 0)));
    g_assert_cmpstr(itemtest->name, ==, "Abel");
    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_nth_item(inventory, 1)));
    g_assert_cmpstr(itemtest->name, ==, "First");
    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_nth_item(inventory, 2)));
    g_assert_cmpstr(itemtest->name, ==, "Fixme");
    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_nth_item(inventory, 3)));
    g_assert_cmpstr(itemtest->name, ==, "Kain");
    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_nth_item(inventory, 4)));
    g_assert_cmpstr(itemtest->name, ==, "Second");

    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_item(inventory, "Fixme")));
    itemtest->value = 3;
    gwy_inventory_rename_item(inventory, "Fixme", "Third");
    g_assert_cmphex(insert_log, ==, 0x0);
    g_assert_cmphex(update_log, ==, 0x5);
    g_assert_cmphex(delete_log, ==, 0x0);
    insert_log = update_log = delete_log = 0;

    g_assert_true((itemtest = (GwyItemTest*)gwy_inventory_get_nth_item(inventory, 4)));
    g_assert_cmpstr(itemtest->name, ==, "Third");

    gwy_inventory_delete_nth_item(inventory, 0);
    gwy_inventory_delete_item(inventory, "Kain");
    g_assert_cmpuint(gwy_inventory_get_n_items(inventory), ==, 3);
    g_assert_cmphex(insert_log, ==, 0);
    g_assert_cmphex(update_log, ==, 0);
    g_assert_cmphex(delete_log, ==, 0x12);
    delete_log = 0;

    g_object_unref(inventory);
    // FIXME: Tradidionally, Inventory has not destroyed items when itself was destroyed. Only old Gwyddion 3 does it.
    // It may be a sensible behaviour but we do not actually expect it now. */
    //g_assert_cmpuint(item_destroy_count, ==, 5);
    g_assert_cmpuint(item_destroy_count, ==, 2);
}

void
test_inventory_default(void)
{
    GwyInventory *inventory = make_simple_inventory();
    guint change_count = 0;
    g_signal_connect_swapped(inventory, "default-changed", G_CALLBACK(record_signal), &change_count);

    g_assert_cmpuint(change_count, ==, 0);
    gwy_inventory_set_default_item_name(inventory, "Default");
    g_assert_cmpuint(change_count, ==, 1);
    g_assert_cmpstr(gwy_inventory_get_default_item_name(inventory), ==, "Default");

    GwyItemTest *itemtest;
    itemtest = gwy_inventory_get_default_item(inventory);
    g_assert_null(itemtest);

    itemtest = gwy_inventory_get_item_or_default(inventory, "No such item");
    g_assert_null(itemtest);

    gwy_inventory_insert_item(inventory, item_new("Not default", 1));
    g_assert_cmpuint(change_count, ==, 1);
    gwy_inventory_insert_item(inventory, item_new("Default", 2));
    g_assert_cmpuint(change_count, ==, 2);
    itemtest = gwy_inventory_get_default_item(inventory);
    g_assert_nonnull(itemtest);
    g_assert_cmpstr(itemtest->name, ==, "Default");

    itemtest = gwy_inventory_get_item_or_default(inventory, "No such item");
    g_assert_nonnull(itemtest);
    g_assert_cmpstr(itemtest->name, ==, "Default");

    itemtest = gwy_inventory_get_item_or_default(inventory, NULL);
    g_assert_nonnull(itemtest);
    g_assert_cmpstr(itemtest->name, ==, "Default");

    gwy_inventory_rename_item(inventory, "Not default", "Still not default");
    g_assert_cmpuint(change_count, ==, 2);
    gwy_inventory_rename_item(inventory, "Default", "No longer default");
    g_assert_cmpuint(change_count, ==, 3);
    gwy_inventory_rename_item(inventory, "Still not default", "Default");
    g_assert_cmpuint(change_count, ==, 4);

    gwy_inventory_set_default_item_name(inventory, "Another");
    g_assert_cmpuint(change_count, ==, 5);
    gwy_inventory_set_default_item_name(inventory, "Default");
    g_assert_cmpuint(change_count, ==, 6);
    gwy_inventory_set_default_item_name(inventory, NULL);
    g_assert_cmpuint(change_count, ==, 7);
    gwy_inventory_set_default_item_name(inventory, NULL);
    g_assert_cmpuint(change_count, ==, 7);
    gwy_inventory_set_default_item_name(inventory, "Default");
    g_assert_cmpuint(change_count, ==, 8);
    gwy_inventory_set_default_item_name(inventory, "Default");
    g_assert_cmpuint(change_count, ==, 8);

    g_object_unref(inventory);
}

static void
check_reordering(G_GNUC_UNUSED GwyInventory *inventory,
                 guint *new_order,
                 gboolean *expect_reorder)
{
    g_assert(expect_reorder);
    // The mapping is new_order[newpos] = oldpos.
    g_assert_cmpuint(new_order[0], ==, 2);
    g_assert_cmpuint(new_order[1], ==, 3);
    g_assert_cmpuint(new_order[2], ==, 1);
    g_assert_cmpuint(new_order[3], ==, 5);
    g_assert_cmpuint(new_order[4], ==, 4);
    g_assert_cmpuint(new_order[5], ==, 0);
}

void
test_inventory_sorting(void)
{
    GwyInventoryItemType item_type = {
        0,
        NULL,
        item_is_const,
        item_get_name,
        item_compare_tricky,
        item_rename,
        item_destroy,
        item_copy,
        NULL,
        NULL,
        NULL,
    };

    item_type.type = GWY_TYPE_ITEM_TEST;
    GwyInventory *inventory = gwy_inventory_new(&item_type);
    g_assert_true(GWY_IS_INVENTORY(inventory));
    gboolean expect_reorder = FALSE;
    item_destroy_count = 0;
    sort_by_name = TRUE;

    guint64 insert_log = 0, update_log = 0, delete_log = 0;
    g_signal_connect(inventory, "item-inserted", G_CALLBACK(record_item_change), &insert_log);
    g_signal_connect(inventory, "item-updated", G_CALLBACK(record_item_change), &update_log);
    g_signal_connect(inventory, "item-deleted", G_CALLBACK(record_item_change), &delete_log);
    g_signal_connect_swapped(inventory, "items-reordered", G_CALLBACK(check_reordering), &expect_reorder);

    gwy_inventory_insert_item(inventory, item_new("Bananna", 2));
    gwy_inventory_insert_item(inventory, item_new("Apple", 8));
    gwy_inventory_insert_item(inventory, item_new("Date", 4));
    gwy_inventory_insert_item(inventory, item_new("Citrus", 1));
    g_assert_cmpuint(gwy_inventory_get_n_items(inventory), ==, 4);
    g_assert_cmphex(insert_log, ==, 0x1133);
    g_assert_cmphex(update_log, ==, 0);
    g_assert_cmphex(delete_log, ==, 0);
    insert_log = 0;

    // Break the sorting order so that followig items are just appended.
    gwy_inventory_insert_nth_item(inventory, item_new("Turd", 0), 2);
    g_assert_cmpuint(gwy_inventory_get_n_items(inventory), ==, 5);
    g_assert_cmphex(insert_log, ==, 0x3);
    g_assert_cmphex(update_log, ==, 0);
    g_assert_cmphex(delete_log, ==, 0);
    insert_log = 0;

    gwy_inventory_insert_item(inventory, item_new("Bogus", 3));
    g_assert_cmpuint(gwy_inventory_get_n_items(inventory), ==, 6);
    g_assert_cmphex(insert_log, ==, 0x6);
    g_assert_cmphex(update_log, ==, 0);
    g_assert_cmphex(delete_log, ==, 0);
    insert_log = 0;

    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Apple"), ==, 0);
    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Bananna"), ==, 1);
    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Turd"), ==, 2);
    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Citrus"), ==, 3);
    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Date"), ==, 4);
    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Bogus"), ==, 5);

    sort_by_name = FALSE;
    expect_reorder = TRUE;
    gwy_inventory_restore_order(inventory);
    g_assert_cmpuint(gwy_inventory_get_n_items(inventory), ==, 6);
    g_assert_cmphex(insert_log, ==, 0);
    g_assert_cmphex(update_log, ==, 0);
    g_assert_cmphex(delete_log, ==, 0);

    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Turd"), ==, 0);
    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Citrus"), ==, 1);
    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Bananna"), ==, 2);
    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Bogus"), ==, 3);
    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Date"), ==, 4);
    g_assert_cmpuint(gwy_inventory_get_item_position(inventory, "Apple"), ==, 5);

    expect_reorder = FALSE;
    g_object_unref(inventory);
    // FIXME: Tradidionally, Inventory has not destroyed items when itself was destroyed. Only old Gwyddion 3 does it.
    // It may be a sensible behaviour but we do not actually expect it now. */
    //g_assert_cmpuint(item_destroy_count, ==, 6);
    g_assert_cmpuint(item_destroy_count, ==, 0);
}

#if 0
static gboolean
predicate1(G_GNUC_UNUSED guint n,
           G_GNUC_UNUSED GObject *item,
           G_GNUC_UNUSED gpointer user_data)
{
    return FALSE;
}

static gboolean
predicate2(G_GNUC_UNUSED guint n,
           G_GNUC_UNUSED GObject *item,
           G_GNUC_UNUSED gpointer user_data)
{
    return TRUE;
}

static gboolean
predicate3(guint n,
           G_GNUC_UNUSED GObject *item,
           G_GNUC_UNUSED gpointer user_data)
{
    return n % 2;
}

static gboolean
predicate4(G_GNUC_UNUSED guint n,
           GObject *item,
           G_GNUC_UNUSED gpointer user_data)
{
    GwyItemTest *itemtest = (GwyItemTest*)item;
    return strlen(itemtest->name) == (guint)itemtest->value;
}

static void
sum_values(G_GNUC_UNUSED guint n,
           GObject *item,
           gpointer user_data)
{
    GwyItemTest *itemtest = (GwyItemTest*)item;
    gint *psum = (gint*)user_data;
    *psum += itemtest->value;
}
#endif
static gboolean
predicate1(G_GNUC_UNUSED gpointer key,
           G_GNUC_UNUSED gpointer item,
           G_GNUC_UNUSED gpointer user_data)
{
    return FALSE;
}

static gboolean
predicate2(G_GNUC_UNUSED gpointer key,
           G_GNUC_UNUSED gpointer item,
           G_GNUC_UNUSED gpointer user_data)
{
    return TRUE;
}

static gboolean
predicate3(gpointer key,
           G_GNUC_UNUSED gpointer item,
           G_GNUC_UNUSED gpointer user_data)
{
    return GPOINTER_TO_UINT(key) % 2;
}

static gboolean
predicate4(G_GNUC_UNUSED gpointer key,
           gpointer item,
           G_GNUC_UNUSED gpointer user_data)
{
    GwyItemTest *itemtest = (GwyItemTest*)item;
    return strlen(itemtest->name) == (guint)itemtest->value;
}

static void
sum_values(G_GNUC_UNUSED gpointer key,
           gpointer item,
           gpointer user_data)
{
    GwyItemTest *itemtest = (GwyItemTest*)item;
    gint *psum = (gint*)user_data;
    *psum += itemtest->value;
}

void
test_inventory_functional(void)
{
    GwyInventoryItemType item_type = {
        0,
        NULL,
        item_is_const,
        item_get_name,
        item_compare,
        item_rename,
        item_destroy,
        item_copy,
        NULL,
        NULL,
        NULL,
    };

    item_type.type = GWY_TYPE_ITEM_TEST;
    GwyInventory *inventory = gwy_inventory_new(&item_type);
    g_assert_true(GWY_IS_INVENTORY(inventory));
    item_destroy_count = 0;
    sort_by_name = TRUE;

    guint64 insert_log = 0, update_log = 0, delete_log = 0;
    g_signal_connect(inventory, "item-inserted", G_CALLBACK(record_item_change), &insert_log);
    g_signal_connect(inventory, "item-updated", G_CALLBACK(record_item_change), &update_log);
    g_signal_connect(inventory, "item-deleted", G_CALLBACK(record_item_change), &delete_log);

    gwy_inventory_insert_item(inventory, item_new("Bananna", 2));
    gwy_inventory_insert_item(inventory, item_new("Apple", 8));
    gwy_inventory_insert_item(inventory, item_new("Date", 4));
    gwy_inventory_insert_item(inventory, item_new("Citrus", 1));
    g_assert_cmpuint(gwy_inventory_get_n_items(inventory), ==, 4);
    g_assert_cmphex(insert_log, ==, 0x1133);
    g_assert_cmphex(update_log, ==, 0);
    g_assert_cmphex(delete_log, ==, 0);
    insert_log = 0;

    GwyItemTest *itemtest = (GwyItemTest*)gwy_inventory_find(inventory, predicate1, NULL);
    g_assert_null(itemtest);

    itemtest = (GwyItemTest*)gwy_inventory_find(inventory, predicate2, NULL);
    g_assert_nonnull(itemtest);
    g_assert_cmpstr(itemtest->name, ==, "Apple");
    g_assert_cmpint(itemtest->value, ==, 8);

    itemtest = (GwyItemTest*)gwy_inventory_find(inventory, predicate3, NULL);
    g_assert_nonnull(itemtest);
    g_assert_cmpstr(itemtest->name, ==, "Bananna");
    g_assert_cmpint(itemtest->value, ==, 2);

    itemtest = (GwyItemTest*)gwy_inventory_find(inventory, predicate4, NULL);
    g_assert_nonnull(itemtest);
    g_assert_cmpstr(itemtest->name, ==, "Date");
    g_assert_cmpint(itemtest->value, ==, 4);

    gint sum = 0;
    gwy_inventory_foreach(inventory, sum_values, &sum);
    g_assert_cmpint(sum, ==, 15);

    g_object_unref(inventory);
    // FIXME: Tradidionally, Inventory has not destroyed items when itself was destroyed. Only old Gwyddion 3 does it.
    // It may be a sensible behaviour but we do not actually expect it now. */
    //g_assert_cmpuint(item_destroy_count, ==, 4);
    g_assert_cmpuint(item_destroy_count, ==, 0);
}

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
