fission-core 0.2.0

Core runtime, state, actions, effects, resources, input, and UI model for Fission
Documentation
use fission_core::{
    with_reducer, AppState, BuildCtx, JobRef, JobResource, ReducerContext, ResourceKey, Runtime,
    TimerResource,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Clone)]
struct TestState {
    ticks: u32,
    last_payload: String,
}

impl AppState for TestState {}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
struct TickPayload {
    label: String,
}

#[fission_macros::fission_reducer(TimerFired)]
fn on_timer_fired(state: &mut TestState, ctx: &mut ReducerContext<TestState>) {
    let payload: TickPayload = ctx.input.timer_tick().expect("timer payload");
    state.ticks += 1;
    state.last_payload = payload.label;
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
struct LoadDetailRequest {
    todo_id: u64,
}

#[derive(Debug)]
struct LoadDetailJob;

impl fission_core::JobSpec for LoadDetailJob {
    type Request = LoadDetailRequest;
    type Ok = String;
    type Err = String;
    const NAME: &'static str = "load-detail";
}

const LOAD_DETAIL: JobRef<LoadDetailJob> = JobRef::new("load-detail");

#[test]
fn job_resources_restart_with_new_generation_when_deps_change() {
    let mut runtime = Runtime::default();
    runtime
        .add_app_state(Box::new(TestState::default()))
        .unwrap();

    let resource = JobResource::new(
        ResourceKey::new("detail"),
        LOAD_DETAIL,
        LoadDetailRequest { todo_id: 1 },
    )
    .deps(1_u64);
    runtime
        .reconcile_resources(vec![fission_core::RuntimeResourceDeclaration {
            key: "detail".into(),
            deps: resource.deps.clone(),
            policy: resource.policy,
            kind: fission_core::RuntimeResourceKind::Job(resource.clone()),
        }])
        .unwrap();

    assert_eq!(runtime.pending_effects.len(), 1);
    let first = runtime.pending_effects[0]
        .resource
        .clone()
        .expect("resource context");

    runtime.pending_effects.clear();
    runtime
        .reconcile_resources(vec![fission_core::RuntimeResourceDeclaration {
            key: "detail".into(),
            deps: resource.deps.clone(),
            policy: resource.policy,
            kind: fission_core::RuntimeResourceKind::Job(resource),
        }])
        .unwrap();
    assert!(runtime.pending_effects.is_empty());

    let changed = JobResource::new(
        ResourceKey::new("detail"),
        LOAD_DETAIL,
        LoadDetailRequest { todo_id: 2 },
    )
    .deps(2_u64);
    runtime
        .reconcile_resources(vec![fission_core::RuntimeResourceDeclaration {
            key: "detail".into(),
            deps: changed.deps.clone(),
            policy: changed.policy,
            kind: fission_core::RuntimeResourceKind::Job(changed),
        }])
        .unwrap();

    assert_eq!(runtime.pending_effects.len(), 1);
    let second = runtime.pending_effects[0]
        .resource
        .clone()
        .expect("resource context");
    assert_ne!(first.generation, second.generation);
}

#[test]
fn timer_resources_dispatch_actions_from_runtime_tick() {
    let mut runtime = Runtime::default();
    runtime
        .add_app_state(Box::new(TestState::default()))
        .unwrap();

    let mut ctx = BuildCtx::new();
    let on_tick = with_reducer!(ctx, TimerFired, on_timer_fired);
    runtime.clear_reducers();
    runtime.absorb_registry(ctx.registry);

    let timer = TimerResource::new(
        ResourceKey::new("refresh"),
        std::time::Duration::from_millis(10),
        TickPayload {
            label: "refresh".into(),
        },
    )
    .immediate()
    .on_tick(on_tick);

    runtime
        .reconcile_resources(vec![fission_core::RuntimeResourceDeclaration {
            key: "refresh".into(),
            deps: timer.deps.clone(),
            policy: timer.policy,
            kind: fission_core::RuntimeResourceKind::Timer(timer),
        }])
        .unwrap();

    runtime.tick(0).unwrap();
    let state = runtime.get_app_state::<TestState>().unwrap();
    assert_eq!(state.ticks, 1);
    assert_eq!(state.last_payload, "refresh");
}