hyperchad_template 0.3.0

HyperChad template package
Documentation
use std::collections::BTreeMap;

use hyperchad_renderer::HtmlTagRenderer;
use hyperchad_renderer_html::{DefaultHtmlTagRenderer, html::elements_to_html};
use hyperchad_template::container;
use hyperchad_transformer::{Number, OverrideCondition, OverrideItem, ResponsiveTrigger};

#[cfg(feature = "logic")]
use hyperchad_template::{AlignItems, LayoutDirection, TextAlign, if_responsive};

#[test]
#[cfg(feature = "logic")]
fn test_responsive_number_attributes() {
    let containers = container! {
        div #test-num padding-x=(if_responsive("mobile").then::<i32>(10).or_else(20)) {
            "Test content"
        }
    };

    assert_eq!(containers.len(), 1);
    let container = &containers[0];

    assert_eq!(container.padding_left, Some(Number::Integer(20)));
    assert_eq!(container.padding_right, Some(Number::Integer(20)));
    assert_eq!(container.overrides.len(), 2);

    let mut found_left = false;
    let mut found_right = false;

    for config in &container.overrides {
        assert_eq!(
            config.condition,
            OverrideCondition::ResponsiveTarget {
                name: "mobile".to_string()
            }
        );
        assert_eq!(config.overrides.len(), 1);

        match &config.overrides[0] {
            OverrideItem::PaddingLeft(value) => {
                assert_eq!(value, &Number::Integer(10));
                assert_eq!(
                    config.default,
                    Some(OverrideItem::PaddingLeft(Number::Integer(20)))
                );
                found_left = true;
            }
            OverrideItem::PaddingRight(value) => {
                assert_eq!(value, &Number::Integer(10));
                assert_eq!(
                    config.default,
                    Some(OverrideItem::PaddingRight(Number::Integer(20)))
                );
                found_right = true;
            }
            item => panic!("unexpected override item: {item:?}"),
        }
    }

    assert!(found_left);
    assert!(found_right);
}

#[test]
#[cfg(feature = "logic")]
fn test_responsive_enum_attributes() {
    let containers = container! {
        div #test-enum
            direction=(
                if_responsive("mobile-large")
                    .then::<LayoutDirection>(LayoutDirection::Column)
                    .or_else(LayoutDirection::Row)
            )
            align-items=(
                if_responsive("mobile")
                    .then::<AlignItems>(AlignItems::Center)
                    .or_else(AlignItems::Start)
            )
        {
            "Test content"
        }
    };

    assert_eq!(containers.len(), 1);
    let container = &containers[0];

    assert_eq!(container.direction, LayoutDirection::Row);
    assert_eq!(container.align_items, Some(AlignItems::Start));
    assert_eq!(container.overrides.len(), 2);

    let mut found_direction = false;
    let mut found_align_items = false;

    for config in &container.overrides {
        match &config.overrides[0] {
            OverrideItem::Direction(value) => {
                assert_eq!(
                    config.condition,
                    OverrideCondition::ResponsiveTarget {
                        name: "mobile-large".to_string()
                    }
                );
                assert_eq!(value, &LayoutDirection::Column);
                assert_eq!(
                    config.default,
                    Some(OverrideItem::Direction(LayoutDirection::Row))
                );
                found_direction = true;
            }
            OverrideItem::AlignItems(value) => {
                assert_eq!(
                    config.condition,
                    OverrideCondition::ResponsiveTarget {
                        name: "mobile".to_string()
                    }
                );
                assert_eq!(value, &AlignItems::Center);
                assert_eq!(
                    config.default,
                    Some(OverrideItem::AlignItems(AlignItems::Start))
                );
                found_align_items = true;
            }
            item => panic!("unexpected override item: {item:?}"),
        }
    }

    assert!(found_direction);
    assert!(found_align_items);
}

#[test]
#[cfg(feature = "logic")]
fn test_responsive_bool_attributes() {
    let containers = container! {
        div #test-bool hidden=(if_responsive("mobile").then::<bool>(true).or_else(false)) {
            "Test content"
        }
    };

    assert_eq!(containers.len(), 1);
    let container = &containers[0];

    assert_eq!(container.hidden, Some(false));
    assert_eq!(container.overrides.len(), 1);

    let config = &container.overrides[0];
    assert_eq!(
        config.condition,
        OverrideCondition::ResponsiveTarget {
            name: "mobile".to_string()
        }
    );
    assert_eq!(config.overrides, vec![OverrideItem::Hidden(true)]);
    assert_eq!(config.default, Some(OverrideItem::Hidden(false)));
}

#[test]
#[cfg(feature = "logic")]
fn test_mixed_responsive_and_static_attributes() {
    let containers = container! {
        div #mixed
            width=(100)
            padding-x=(if_responsive("mobile").then::<i32>(10).or_else(20))
            direction=(LayoutDirection::Row)
            text-align=(
                if_responsive("tablet")
                    .then::<TextAlign>(TextAlign::Center)
                    .or_else(TextAlign::Start)
            )
        {
            "Mixed content"
        }
    };

    assert_eq!(containers.len(), 1);
    let container = &containers[0];

    assert_eq!(container.width, Some(Number::Integer(100)));
    assert_eq!(container.direction, LayoutDirection::Row);
    assert_eq!(container.padding_left, Some(Number::Integer(20)));
    assert_eq!(container.padding_right, Some(Number::Integer(20)));
    assert_eq!(container.text_align, Some(TextAlign::Start));

    assert_eq!(container.overrides.len(), 3);
}

#[test]
#[cfg(feature = "logic")]
fn test_responsive_margin_shorthand_attributes() {
    let containers = container! {
        div #test-margin margin=(if_responsive("mobile").then::<i32>(10).or_else(20)) {
            "Margin test"
        }
    };

    let container = &containers[0];

    assert_eq!(container.margin_top, Some(Number::Integer(20)));
    assert_eq!(container.margin_right, Some(Number::Integer(20)));
    assert_eq!(container.margin_bottom, Some(Number::Integer(20)));
    assert_eq!(container.margin_left, Some(Number::Integer(20)));
    assert_eq!(container.overrides.len(), 4);
}

#[test]
#[cfg(feature = "logic")]
fn test_responsive_gap_shorthand_attributes() {
    let containers = container! {
        div #test-gap gap=(if_responsive("mobile").then::<i32>(8).or_else(16)) {
            "Gap test"
        }
    };

    let container = &containers[0];

    assert_eq!(container.column_gap, Some(Number::Integer(16)));
    assert_eq!(container.row_gap, Some(Number::Integer(16)));
    assert_eq!(container.overrides.len(), 2);

    let mut found_column_gap = false;
    let mut found_row_gap = false;
    for config in &container.overrides {
        assert_eq!(
            config.condition,
            OverrideCondition::ResponsiveTarget {
                name: "mobile".to_string()
            }
        );
        match &config.overrides[0] {
            OverrideItem::ColumnGap(value) => {
                assert_eq!(value, &Number::Integer(8));
                assert_eq!(
                    config.default,
                    Some(OverrideItem::ColumnGap(Number::Integer(16)))
                );
                found_column_gap = true;
            }
            OverrideItem::RowGap(value) => {
                assert_eq!(value, &Number::Integer(8));
                assert_eq!(
                    config.default,
                    Some(OverrideItem::RowGap(Number::Integer(16)))
                );
                found_row_gap = true;
            }
            item => panic!("unexpected override item: {item:?}"),
        }
    }

    assert!(found_column_gap);
    assert!(found_row_gap);
}

#[test]
#[cfg(feature = "logic")]
fn test_responsive_border_radius_shorthand_attributes() {
    let containers = container! {
        div #test-radius border-radius=(if_responsive("mobile").then::<i32>(6).or_else(12)) {
            "Radius test"
        }
    };

    let container = &containers[0];

    assert_eq!(container.border_top_left_radius, Some(Number::Integer(12)));
    assert_eq!(container.border_top_right_radius, Some(Number::Integer(12)));
    assert_eq!(
        container.border_bottom_left_radius,
        Some(Number::Integer(12))
    );
    assert_eq!(
        container.border_bottom_right_radius,
        Some(Number::Integer(12))
    );
    assert_eq!(container.overrides.len(), 4);
}

#[test]
#[cfg(feature = "logic")]
fn test_responsive_border_shorthand_attributes() {
    let containers = container! {
        div #test-border
            border=(
                if_responsive("mobile")
                    .then::<(i32, &str)>((2, "#111111"))
                    .or_else((1, "#222222"))
            )
        {
            "Border test"
        }
    };

    let container = &containers[0];

    assert!(container.border_top.is_some());
    assert!(container.border_right.is_some());
    assert!(container.border_bottom.is_some());
    assert!(container.border_left.is_some());
    assert_eq!(container.overrides.len(), 4);

    let mut border_override_count = 0;
    for config in &container.overrides {
        assert_eq!(
            config.condition,
            OverrideCondition::ResponsiveTarget {
                name: "mobile".to_string()
            }
        );
        match &config.overrides[0] {
            OverrideItem::BorderTop((_, width))
            | OverrideItem::BorderRight((_, width))
            | OverrideItem::BorderBottom((_, width))
            | OverrideItem::BorderLeft((_, width)) => {
                assert_eq!(width, &Number::Integer(2));
                border_override_count += 1;
            }
            item => panic!("unexpected override item: {item:?}"),
        }
    }

    assert_eq!(border_override_count, 4);
}

#[test]
#[cfg(feature = "logic")]
fn test_responsive_overrides_render_media_query_css() {
    let containers = container! {
        div #responsive-css-test
            padding-left=(if_responsive("mobile").then::<i32>(10).or_else(24))
        {
            "Responsive css"
        }
    };

    let mut renderer = DefaultHtmlTagRenderer::default()
        .with_responsive_trigger("mobile", ResponsiveTrigger::MaxWidth(Number::Integer(768)));
    renderer.add_responsive_trigger(
        "tablet".to_string(),
        ResponsiveTrigger::MaxWidth(Number::Integer(1024)),
    );

    let mut content = Vec::new();
    elements_to_html(&mut content, &containers, &renderer, false).unwrap();
    let content = String::from_utf8(content).unwrap();

    let html = renderer.root_html(
        &BTreeMap::new(),
        &containers[0],
        content,
        None,
        None,
        None,
        None,
        &[],
        &[],
        &[],
    );

    assert!(html.contains("@media(max-width:768px)"));
    assert!(html.contains("#responsive-css-test"));
    assert!(html.contains("padding-left:10px !important;"));
}