tauri-plugin-syncular 0.34.0

A native syncular client inside the Tauri process, exposed to the webview as commands + events (consumes the Rust client core directly — no FFI)
Documentation
//! Mock-runtime tests: build a real `MockRuntime` Tauri app with the plugin
//! registered and a webview window, exercising the plugin's SHELL as far as the
//! mock runtime allows.
//!
//! ## Why this file is thin (honest note)
//!
//! The plugin's command BEHAVIOR is exhaustively covered by the crate's unit
//! tests — `core::tests` (router round-trip, event derivation, file-DB
//! persistence, config validation) and `tests::owner_thread_round_trips_over_
//! mailbox` (the real mailbox host path the Tauri commands forward onto). Those
//! run with zero Tauri machinery.
//!
//! Invoking the plugin's commands THROUGH the mock IPC additionally requires
//! the plugin's ACL manifest to be present in the runtime authority. That
//! manifest is produced by `tauri_build`/`generate_context!` at the CONSUMING
//! app's build time (from the plugin's generated `permissions/`), not by
//! `mock_context(noop_assets())` — a mock context carries no plugin manifests,
//! so a `get_ipc_response` for `plugin:syncular|…` is denied with
//! `UnknownManifest`. Rather than stand up a full `tauri.conf.json` +
//! capabilities + build-codegen fixture just to re-prove the two-line channel
//! forward the commands are, this file verifies what the mock runtime CAN
//! prove: the plugin registers, its `setup` runs (spawning the owning thread
//! and managing the mailbox state), and a window builds against it. The
//! *example app* (bindings/tauri/example) is the end-to-end wiring proof with a
//! real capabilities file.
//!
//! Building a webview needs a windowing backend a headless CI box may lack;
//! each test SKIPS (prints + returns) rather than fails when the window cannot
//! be created, keeping the suite green on a headless runner.

use tauri::test::{mock_builder, mock_context, noop_assets};
use tauri::WebviewWindowBuilder;

fn build_app() -> tauri::App<tauri::test::MockRuntime> {
    let config = tauri_plugin_syncular::SyncularConfig {
        auto_sync: false,
        ..Default::default()
    };
    mock_builder()
        .plugin(tauri_plugin_syncular::init(config))
        .build(mock_context(noop_assets()))
        .expect("failed to build mock app with the syncular plugin")
}

#[test]
fn plugin_registers_and_setup_runs() {
    // Building the app runs the plugin's `setup` hook: it manages the mailbox
    // state and spawns the `syncular-core` owning thread. A panic or error in
    // setup would fail `.build()`; reaching here proves the shell initializes
    // and the plugin is registered under its name.
    let app = build_app();
    // Let the owning thread come up; dropping the app tears it down cleanly
    // (the mailbox Sender drops → the thread's recv disconnects → shutdown).
    std::thread::sleep(std::time::Duration::from_millis(20));
    drop(app);
}

#[test]
fn window_builds_against_the_plugin() {
    let app = build_app();
    // A window built with the plugin registered: the invoke handler is
    // installed. Granting its ACL and invoking commands is the consuming app's
    // concern (proven end-to-end by bindings/tauri/example). On a headless box
    // with no windowing backend this skips rather than fails.
    match WebviewWindowBuilder::new(&app, "main", Default::default()).build() {
        Ok(_window) => {}
        Err(e) => eprintln!("skipping window build: no windowing backend ({e})"),
    }
}

/// Compiled as a dependency (not `cfg(test)`), so the clock seam is only
/// reachable through the public `test-clock` feature.
#[cfg(feature = "test-clock")]
#[test]
fn consumer_sets_the_client_clock_through_the_public_seam() {
    let mut core =
        tauri_plugin_syncular::core::SyncularCore::new(&serde_json::json!({})).expect("build core");
    assert_eq!(
        core.set_now_ms(1_000).unwrap_err(),
        "client.not_created: create a client before setting the clock"
    );
    let created = core.command(&serde_json::json!({
        "method": "create",
        "params": {
            "clientId": "consumer-clock",
            "schema": { "version": 1, "tables": [] },
            "nowMs": 1_000
        }
    }));
    assert!(created.get("error").is_none(), "{created}");
    core.set_now_ms(2_000).expect("set the clock");
}