#![allow(unused_variables)]
mod mock_runtime;
pub use mock_runtime::*;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json::Value as JsonValue;
use std::{
borrow::Cow,
collections::HashMap,
fmt::Debug,
hash::{Hash, Hasher},
sync::{
mpsc::{channel, Sender},
Arc, Mutex,
},
};
use crate::hooks::window_invoke_responder;
#[cfg(shell_scope)]
use crate::ShellScopeConfig;
use crate::{api::ipc::CallbackFn, App, Builder, Context, InvokePayload, Manager, Pattern, Window};
use tauri_utils::{
assets::{AssetKey, Assets, CspHash},
config::{CliConfig, Config, PatternKind, TauriConfig},
};
pub const INVOKE_KEY: &str = "__invoke-key__";
#[derive(Eq, PartialEq)]
struct IpcKey {
callback: CallbackFn,
error: CallbackFn,
}
impl Hash for IpcKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.callback.0.hash(state);
self.error.0.hash(state);
}
}
struct Ipc(Mutex<HashMap<IpcKey, Sender<std::result::Result<JsonValue, JsonValue>>>>);
pub struct NoopAsset {
assets: HashMap<&'static str, &'static [u8]>,
csp_hashes: Vec<CspHash<'static>>,
}
impl Assets for NoopAsset {
fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
None
}
fn iter(&self) -> Box<dyn Iterator<Item = (&&str, &&[u8])> + '_> {
Box::new(self.assets.iter())
}
fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
Box::new(self.csp_hashes.iter().copied())
}
}
pub fn noop_assets() -> NoopAsset {
NoopAsset {
assets: Default::default(),
csp_hashes: Default::default(),
}
}
pub fn mock_context<A: Assets>(assets: A) -> crate::Context<A> {
Context {
config: Config {
schema: None,
package: Default::default(),
tauri: TauriConfig {
pattern: PatternKind::Brownfield,
windows: vec![Default::default()],
cli: Some(CliConfig {
description: None,
long_description: None,
before_help: None,
after_help: None,
args: None,
subcommands: None,
}),
bundle: Default::default(),
allowlist: Default::default(),
security: Default::default(),
updater: Default::default(),
system_tray: None,
macos_private_api: false,
},
build: Default::default(),
plugins: Default::default(),
},
#[cfg(dev)]
config_parent: None,
assets: Arc::new(assets),
default_window_icon: None,
app_icon: None,
system_tray_icon: None,
package_info: crate::PackageInfo {
name: "test".into(),
version: "0.1.0".parse().unwrap(),
authors: "Tauri",
description: "Tauri test",
},
_info_plist: (),
pattern: Pattern::Brownfield(std::marker::PhantomData),
#[cfg(shell_scope)]
shell_scope: ShellScopeConfig {
open: None,
scopes: HashMap::new(),
},
}
}
pub fn mock_builder() -> Builder<MockRuntime> {
let mut builder = Builder::<MockRuntime>::new().manage(Ipc(Default::default()));
builder.invoke_key = INVOKE_KEY.to_string();
builder.invoke_responder = Arc::new(|window, response, callback, error| {
let window_ = window.clone();
let ipc = window_.state::<Ipc>();
let mut ipc_ = ipc.0.lock().unwrap();
if let Some(tx) = ipc_.remove(&IpcKey { callback, error }) {
tx.send(response.into_result()).unwrap();
} else {
window_invoke_responder(window, response, callback, error)
}
});
builder
}
pub fn mock_app() -> App<MockRuntime> {
mock_builder().build(mock_context(noop_assets())).unwrap()
}
pub fn assert_ipc_response<T: Serialize + Debug>(
window: &Window<MockRuntime>,
payload: InvokePayload,
expected: Result<T, T>,
) {
assert_eq!(
get_ipc_response(window, payload),
expected
.map(|e| serde_json::to_value(e).unwrap())
.map_err(|e| serde_json::to_value(e).unwrap())
);
}
pub fn get_ipc_response<T: DeserializeOwned + Debug>(
window: &Window<MockRuntime>,
payload: InvokePayload,
) -> Result<T, T> {
let callback = payload.callback;
let error = payload.error;
let ipc = window.state::<Ipc>();
let (tx, rx) = channel();
ipc.0.lock().unwrap().insert(IpcKey { callback, error }, tx);
window.clone().on_message(payload).unwrap();
let res: Result<JsonValue, JsonValue> = rx.recv().expect("Failed to receive result from command");
res
.map(|v| serde_json::from_value(v).unwrap())
.map_err(|e| serde_json::from_value(e).unwrap())
}
#[cfg(test)]
pub(crate) fn mock_invoke_context() -> crate::endpoints::InvokeContext<MockRuntime> {
let app = mock_app();
crate::endpoints::InvokeContext {
window: app.get_window("main").unwrap(),
config: app.config(),
package_info: app.package_info().clone(),
}
}
#[cfg(test)]
mod tests {
use crate::Manager;
use std::time::Duration;
use super::mock_app;
#[test]
fn run_app() {
let app = mock_app();
let w = app.get_window("main").unwrap();
std::thread::spawn(move || {
std::thread::sleep(Duration::from_secs(1));
w.close().unwrap();
});
app.run(|_app, event| {
println!("{:?}", event);
});
}
}