nrelm 0.1.0

An idiomatic GUI library inspired by Elm and based on gtk3-rs
use crate::{
    RelmIterChildrenExt, WidgetRef,
    factory::{FactoryView, positions::GridPosition},
    run_gtk_test,
};
use gtk::prelude::BinExt;

/// Assert the exact ordering of widget children. The container must implement `RelmIterChildrenExt`.
macro_rules! assert_children {
    ($container:ident: None) => {
        if let Some(child) = $container.iter_children().next() {
            panic!("Container expected be empty, but there is at least one child: {child:?}");
        }
    };
    ($container:ident: $($child:ident),*) => {
        {
            let mut actual_children = $container.iter_children();
            let mut index = 0;
            $(
                let actual = actual_children.next();
                let expected = &$child;
                if  actual.as_ref().map(|widget| widget.widget_ref()) != Some(expected.widget_ref()) {
                    panic!(
                        "Children at index {index} does not match; expected: Some({expected:?}), actual: {actual:?}"
                    )
                }
                index += 1;
            )+
            if let Some(actual) = actual_children.next() {
                panic!("Children at index {index} does not match; expected: None, actual: {actual:?}");
            }
        }
    };
}

#[test]
fn box_factory_view() {
    run_gtk_test(|| {
        let gtk_box = gtk::Box::default();

        let widget1 = gtk::Label::default();
        let widget2 = gtk::Switch::default();
        let widget3 = gtk::Entry::default();

        let w2 = gtk_box.factory_append(&widget2, &());
        let w3 = gtk_box.factory_insert_after(&widget3, &(), &w2);
        let w1 = gtk_box.factory_prepend(&widget1, &());

        assert_eq!(widget1, w1);
        assert_eq!(widget2, w2);
        assert_eq!(widget3, w3);

        assert_eq!(gtk::Box::returned_widget_to_child(&w1), widget1);
        assert_eq!(gtk::Box::returned_widget_to_child(&w2), widget2);
        assert_eq!(gtk::Box::returned_widget_to_child(&w3), widget3);

        assert_children!(gtk_box: w1, w2, w3);

        gtk_box.factory_move_after(&w3, &w1);
        assert_children!(gtk_box: w1, w3, w2);

        gtk_box.factory_move_after(&w1, &w2);
        assert_children!(gtk_box: w3, w2, w1);

        gtk_box.factory_move_start(&w2);
        assert_children!(gtk_box: w2, w3, w1);

        gtk_box.factory_move_start(&w1);
        assert_children!(gtk_box: w1, w2, w3);

        gtk_box.factory_remove(&w3);
        assert_children!(gtk_box: w1, w2);

        gtk_box.factory_remove(&w2);
        assert_children!(gtk_box: w1);

        gtk_box.factory_remove(&w1);
        assert_children!(gtk_box: None);
    });
}

#[test]
fn grid_factory_view() {
    run_gtk_test(|| {
        let grid = gtk::Grid::default();

        let widget1 = gtk::Label::default();
        let widget2 = gtk::Switch::default();
        let widget3 = gtk::Entry::default();

        let pos1 = GridPosition {
            column: 0,
            row: 0,
            width: 1,
            height: 1,
        };
        let pos2 = GridPosition {
            column: 1,
            row: 0,
            width: 1,
            height: 2,
        };
        let pos3 = GridPosition {
            column: 0,
            row: 1,
            width: 1,
            height: 1,
        };

        let w2 = grid.factory_append(&widget2, &pos2);
        let w3 = grid.factory_append(&widget3, &pos3);
        let w1 = grid.factory_append(&widget1, &pos1);

        assert_eq!(widget1, w1);
        assert_eq!(widget2, w2);
        assert_eq!(widget3, w3);

        assert_eq!(gtk::Grid::returned_widget_to_child(&w1), widget1);
        assert_eq!(gtk::Grid::returned_widget_to_child(&w2), widget2);
        assert_eq!(gtk::Grid::returned_widget_to_child(&w3), widget3);

        // GTK3 returns Grid children in reverse insertion order.
        assert_children!(grid: w1, w3, w2);

        grid.factory_remove(&w3);
        assert_children!(grid: w1, w2);

        grid.factory_remove(&w2);
        assert_children!(grid: w1);

        grid.factory_remove(&w1);
        assert_children!(grid: None);
    });
}

#[test]
fn stack_factory_view() {
    run_gtk_test(|| {
        let stack = gtk::Stack::default();

        let widget1 = gtk::Label::default();
        let widget2 = gtk::Switch::default();
        let widget3 = gtk::Entry::default();

        let w1 = stack.factory_append(&widget1, &());
        let w2 = stack.factory_append(&widget2, &());
        let w3 = stack.factory_append(&widget3, &());

        assert_eq!(w1, widget1);
        assert_eq!(w2, widget2);
        assert_eq!(w3, widget3);

        assert_eq!(gtk::Stack::returned_widget_to_child(&w1), widget1);
        assert_eq!(gtk::Stack::returned_widget_to_child(&w2), widget2);
        assert_eq!(gtk::Stack::returned_widget_to_child(&w3), widget3);

        assert_children!(stack: widget1, widget2, widget3);

        stack.factory_remove(&w2);
        assert_children!(stack: widget1, widget3);

        stack.factory_remove(&w3);
        assert_children!(stack: widget1);

        stack.factory_remove(&w1);
        assert_children!(stack: None);
    });
}

#[test]
fn list_box_factory_view() {
    run_gtk_test(|| {
        let list_box = gtk::ListBox::default();

        let widget1 = gtk::Label::default();
        let widget2 = gtk::ListBoxRow::default();
        let widget3 = gtk::Entry::default();

        let row2 = list_box.factory_append(&widget2, &());
        let row3 = list_box.factory_insert_after(&widget3, &(), &row2);
        let row1 = list_box.factory_prepend(&widget1, &());

        assert_eq!(row1.child().as_ref(), Some(widget1.as_ref()));
        assert_eq!(row2, widget2);
        assert_eq!(row3.child().as_ref(), Some(widget3.as_ref()));

        assert_eq!(gtk::ListBox::returned_widget_to_child(&row1), widget1);
        assert_eq!(gtk::ListBox::returned_widget_to_child(&row2), widget2);
        assert_eq!(gtk::ListBox::returned_widget_to_child(&row3), widget3);

        assert_children!(list_box: row1, row2, row3);

        list_box.factory_move_after(&row3, &row1);
        assert_children!(list_box: row1, row3, row2);

        list_box.factory_move_after(&row1, &row2);
        assert_children!(list_box: row3, row2, row1);

        list_box.factory_move_start(&row2);
        assert_children!(list_box: row2, row3, row1);

        list_box.factory_move_start(&row1);
        assert_children!(list_box: row1, row2, row3);

        list_box.factory_remove(&row3);
        assert_children!(list_box: row1, row2);

        list_box.factory_remove(&row2);
        assert_children!(list_box: row1);

        list_box.factory_remove(&row1);
        assert_children!(list_box: None);
    });
}

#[test]
fn flow_box_factory_view() {
    run_gtk_test(|| {
        let flow_box = gtk::FlowBox::default();

        let widget1 = gtk::Label::default();
        let widget2 = gtk::FlowBoxChild::default();
        let widget3 = gtk::Entry::default();

        let child2 = flow_box.factory_append(&widget2, &());
        let child3 = flow_box.factory_insert_after(&widget3, &(), &child2);
        let child1 = flow_box.factory_prepend(&widget1, &());

        assert_eq!(child1.child().as_ref(), Some(widget1.as_ref()));
        assert_eq!(child2, widget2);
        assert_eq!(child3.child().as_ref(), Some(widget3.as_ref()));

        assert_eq!(gtk::FlowBox::returned_widget_to_child(&child1), widget1);
        assert_eq!(gtk::FlowBox::returned_widget_to_child(&child2), widget2);
        assert_eq!(gtk::FlowBox::returned_widget_to_child(&child3), widget3);

        assert_children!(flow_box: child1, child2, child3);

        flow_box.factory_move_after(&child3, &child1);
        assert_children!(flow_box: child1, child3, child2);

        flow_box.factory_move_after(&child1, &child2);
        assert_children!(flow_box: child3, child2, child1);

        flow_box.factory_move_start(&child2);
        assert_children!(flow_box: child2, child3, child1);

        flow_box.factory_move_start(&child1);
        assert_children!(flow_box: child1, child2, child3);

        flow_box.factory_remove(&child3);
        assert_children!(flow_box: child1, child2);

        flow_box.factory_remove(&child2);
        assert_children!(flow_box: child1);

        flow_box.factory_remove(&child1);
        assert_children!(flow_box: None);
    });
}