fission-test 0.6.1

Headless and runtime testing helpers for Fission applications
Documentation
use fission_core::internal::{InternalLowerer, InternalRenderNode};
use fission_core::ui::{Container, Video, Widget};
use fission_core::{GlobalState, WidgetId};
use fission_ir::{EmbedKind, LayoutOp, Op};
use fission_render::DisplayOp;
use fission_test::TestHarness;
use fission_widgets::WebView;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
struct EmbedState;
impl GlobalState for EmbedState {}

fn display_surfaces(harness: &TestHarness<EmbedState>) -> Vec<DisplayOp> {
    harness
        .get_last_display_list()
        .expect("display list")
        .ops
        .into_iter()
        .filter(|op| matches!(op, DisplayOp::DrawSurface { .. }))
        .collect()
}

#[test]
fn video_embed_registers_runtime_state_and_draws_surface() {
    let widget_id = WidgetId::explicit("test.video");
    let mut harness = TestHarness::new(EmbedState).with_root_widget(Video {
        id: Some(widget_id),
        source: "fixtures/demo.mp4".into(),
        width: Some(320.0),
        height: Some(180.0),
        autoplay: true,
        loop_playback: true,
    });

    harness.pump().expect("pump video embed");

    let video_state = harness
        .runtime
        .runtime_state
        .video
        .states
        .get(&widget_id)
        .expect("video registration should sync into runtime state");
    assert_eq!(video_state.asset_source, "fixtures/demo.mp4");
    assert!(video_state.looped);

    let ir = harness.last_ir.as_ref().expect("ir");
    assert!(ir.nodes.values().any(|node| matches!(
        node.op,
        Op::Layout(LayoutOp::Embed {
            kind: EmbedKind::Video,
            widget_id: id,
            ..
        }) if id == widget_id
    )));

    let surfaces = display_surfaces(&harness);
    assert_eq!(surfaces.len(), 1);
    match &surfaces[0] {
        DisplayOp::DrawSurface { rect, .. } => {
            assert_eq!(rect.size.width, 320.0);
            assert_eq!(rect.size.height, 180.0);
        }
        other => panic!("expected video surface, got {other:?}"),
    }
}

#[derive(Clone)]
struct WebApp;
impl From<WebApp> for Widget {
    fn from(_component: WebApp) -> Self {
        let (_ctx, _view) = fission_core::build::current::<EmbedState>();
        Container::new(WebView {
            id: WidgetId::explicit("test.web"),
            url: "https://example.test/docs".into(),
            user_agent: Some("FissionTest/1".into()),
            width: Some(320.0),
            height: Some(180.0),
        })
        .width(320.0)
        .height(180.0)
        .into()
    }
}
#[test]
fn webview_embed_registers_runtime_state_and_draws_surface() {
    let widget_id = WidgetId::explicit("test.web");
    let mut harness = TestHarness::new(EmbedState).with_root_widget(WebApp);

    harness.pump().expect("pump web embed");

    let web_state = harness
        .runtime
        .runtime_state
        .web
        .states
        .get(&widget_id)
        .expect("web registration should sync into runtime state");
    assert_eq!(web_state.url, "https://example.test/docs");
    assert_eq!(web_state.user_agent.as_deref(), Some("FissionTest/1"));

    let ir = harness.last_ir.as_ref().expect("ir");
    assert!(ir.nodes.values().any(|node| matches!(
        node.op,
        Op::Layout(LayoutOp::Embed {
            kind: EmbedKind::Web,
            widget_id: id,
            ..
        }) if id == widget_id
    )));

    assert_eq!(display_surfaces(&harness).len(), 1);
}

#[derive(Clone)]
struct CustomEmbedApp;
impl From<CustomEmbedApp> for Widget {
    fn from(_component: CustomEmbedApp) -> Self {
        let (_ctx, _view) = fission_core::build::current::<EmbedState>();
        fission_core::internal::custom_render_widget(InternalRenderNode {
            debug_tag: "TestCustomEmbed".into(),
            lowerer: Some(Arc::new(CustomEmbedInternalLowerer)),
            render_object: None,
        })
    }
}
#[derive(Debug)]
struct CustomEmbedInternalLowerer;

impl InternalLowerer for CustomEmbedInternalLowerer {
    fn lower_dyn(&self, cx: &mut fission_core::internal::InternalLoweringCx) -> WidgetId {
        let node_id = cx.next_node_id();
        cx.insert_node(
            node_id,
            Op::Layout(LayoutOp::Embed {
                kind: EmbedKind::Custom(vec![1, 2, 3]),
                widget_id: WidgetId::explicit("test.custom"),
                width: Some(240.0),
                height: Some(120.0),
            }),
            vec![],
        )
    }

    fn stable_key(&self) -> u64 {
        0xF151_C057
    }
}

#[test]
fn custom_embed_draws_surface() {
    let mut harness = TestHarness::new(EmbedState).with_root_widget(CustomEmbedApp);

    harness.pump().expect("pump custom embed");

    let surfaces = display_surfaces(&harness);
    assert_eq!(surfaces.len(), 1);
    match &surfaces[0] {
        DisplayOp::DrawSurface { rect, .. } => {
            assert_eq!(rect.size.width, 240.0);
            assert_eq!(rect.size.height, 120.0);
        }
        other => panic!("expected custom surface, got {other:?}"),
    }
}