feat(core): allow the MockRuntime to call a command and get a return value + fix doctest (#8380)

* Add `get_ipc_response` to the `MockRuntime`

* Fix `MockRuntime` doctests

* Add support for raw ipc

* cleanup, add change file

* only a single get fn

---------

Co-authored-by: Lucas Nogueira <lucas@crabnebula.dev>
This commit is contained in:
Alexandre Dang 2023-12-14 15:52:25 +01:00 committed by GitHub
parent 5848b4e8e9
commit db12777742
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 116 additions and 97 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch:enhance
---
Added `test::get_ipc_response`.

View File

@ -11,47 +11,38 @@
//! # Examples
//!
//! ```rust
//! #[tauri::command]
//! fn my_cmd() {}
//! use tauri::test::{mock_builder, mock_context, noop_assets};
//!
//! fn create_app<R: tauri::Runtime>(mut builder: tauri::Builder<R>) -> tauri::App<R> {
//! #[tauri::command]
//! fn ping() -> &'static str {
//! "pong"
//! }
//!
//! fn create_app<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::App<R> {
//! builder
//! .setup(|app| {
//! // do something
//! Ok(())
//! })
//! .invoke_handler(tauri::generate_handler![my_cmd])
//! // remove the string argument on your app
//! .invoke_handler(tauri::generate_handler![ping])
//! // remove the string argument to use your app's config file
//! .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
//! .expect("failed to build app")
//! }
//!
//! fn main() {
//! // let app = create_app(tauri::Builder::default());
//! // app.run(|_handle, _event| {});
//! }
//! let app = create_app(mock_builder());
//! let window = tauri::WindowBuilder::new(&app, "main", Default::default())
//! .build()
//! .unwrap();
//!
//! //#[cfg(test)]
//! mod tests {
//! use tauri::Manager;
//! //#[cfg(test)]
//! fn something() {
//! let app = super::create_app(tauri::test::mock_builder());
//! let window = app.get_window("main").unwrap();
//! // do something with the app and window
//! // in this case we'll run the my_cmd command with no arguments
//! tauri::test::assert_ipc_response(
//! // run the `ping` command and assert it returns `pong`
//! let res = tauri::test::get_ipc_response(
//! &window,
//! tauri::window::InvokeRequest {
//! cmd: "my_cmd".into(),
//! cmd: "ping".into(),
//! callback: tauri::ipc::CallbackFn(0),
//! error: tauri::ipc::CallbackFn(1),
//! body: serde_json::Value::Null.into(),
//! body: tauri::ipc::InvokeBody::default(),
//! headers: Default::default(),
//! },
//! Ok(())
//! );
//! }
//! ).map(|b| b.deserialize::<String>().unwrap());
//! }
//! ```
@ -61,14 +52,10 @@ mod mock_runtime;
pub use mock_runtime::*;
use serde::Serialize;
use std::{
borrow::Cow,
fmt::Debug,
hash::{Hash, Hasher},
};
use std::{borrow::Cow, fmt::Debug};
use crate::{
ipc::{CallbackFn, InvokeResponse},
ipc::{InvokeBody, InvokeError, InvokeResponse},
window::InvokeRequest,
App, Builder, Context, Pattern, Window,
};
@ -77,19 +64,6 @@ use tauri_utils::{
config::{Config, PatternKind, TauriConfig},
};
#[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);
}
}
/// An empty [`Assets`] implementation.
pub struct NoopAsset {
csp_hashes: Vec<CspHash<'static>>,
@ -175,32 +149,26 @@ pub fn mock_app() -> App<MockRuntime> {
/// # Examples
///
/// ```rust
/// use tauri::test::{mock_builder, mock_context, noop_assets};
///
/// #[tauri::command]
/// fn ping() -> &'static str {
/// "pong"
/// }
///
/// fn create_app<R: tauri::Runtime>(mut builder: tauri::Builder<R>) -> tauri::App<R> {
/// fn create_app<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::App<R> {
/// builder
/// .invoke_handler(tauri::generate_handler![ping])
/// // remove the string argument on your app
/// // remove the string argument to use your app's config file
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
/// .expect("failed to build app")
/// }
///
/// fn main() {
/// // let app = create_app(tauri::Builder::default());
/// // app.run(|_handle, _event| {});}
/// }
///
/// //#[cfg(test)]
/// mod tests {
/// use tauri::Manager;
///
/// //#[cfg(test)]
/// fn something() {
/// let app = super::create_app(tauri::test::mock_builder());
/// let window = app.get_window("main").unwrap();
/// let app = create_app(mock_builder());
/// let window = tauri::WindowBuilder::new(&app, "main", Default::default())
/// .build()
/// .unwrap();
///
/// // run the `ping` command and assert it returns `pong`
/// tauri::test::assert_ipc_response(
@ -209,40 +177,86 @@ pub fn mock_app() -> App<MockRuntime> {
/// cmd: "ping".into(),
/// callback: tauri::ipc::CallbackFn(0),
/// error: tauri::ipc::CallbackFn(1),
/// body: serde_json::Value::Null.into(),
/// body: tauri::ipc::InvokeBody::default(),
/// headers: Default::default(),
/// },
/// // the expected response is a success with the "pong" payload
/// // we could also use Err("error message") here to ensure the command failed
/// Ok("pong")
/// );
/// }
/// }
/// ```
pub fn assert_ipc_response<T: Serialize + Debug + Send + Sync + 'static>(
window: &Window<MockRuntime>,
request: InvokeRequest,
expected: Result<T, T>,
) {
let (tx, rx) = std::sync::mpsc::sync_channel(1);
window.clone().on_message(
request,
Box::new(move |_window, _cmd, response, _callback, _error| {
let response =
get_ipc_response(window, request).map(|b| b.deserialize::<serde_json::Value>().unwrap());
assert_eq!(
match response {
InvokeResponse::Ok(b) => Ok(b.into_json()),
InvokeResponse::Err(e) => Err(e.0),
},
response,
expected
.map(|e| serde_json::to_value(e).unwrap())
.map_err(|e| serde_json::to_value(e).unwrap())
);
}
tx.send(()).unwrap();
/// Executes the given IPC message and get the return value.
///
/// # Examples
///
/// ```rust
/// use tauri::test::{mock_builder, mock_context, noop_assets};
///
/// #[tauri::command]
/// fn ping() -> &'static str {
/// "pong"
/// }
///
/// fn create_app<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::App<R> {
/// builder
/// .invoke_handler(tauri::generate_handler![ping])
/// // remove the string argument to use your app's config file
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
/// .expect("failed to build app")
/// }
///
/// fn main() {
/// let app = create_app(mock_builder());
/// let window = tauri::WindowBuilder::new(&app, "main", Default::default())
/// .build()
/// .unwrap();
///
/// // run the `ping` command and assert it returns `pong`
/// let res = tauri::test::get_ipc_response(
/// &window,
/// tauri::window::InvokeRequest {
/// cmd: "ping".into(),
/// callback: tauri::ipc::CallbackFn(0),
/// error: tauri::ipc::CallbackFn(1),
/// body: tauri::ipc::InvokeBody::default(),
/// headers: Default::default(),
/// },
/// );
/// assert!(res.is_ok());
/// assert_eq!(res.unwrap().deserialize::<String>().unwrap(), String::from("pong"));
/// }
///```
pub fn get_ipc_response(
window: &Window<MockRuntime>,
request: InvokeRequest,
) -> Result<InvokeBody, serde_json::Value> {
let (tx, rx) = std::sync::mpsc::sync_channel(1);
window.clone().on_message(
request,
Box::new(move |_window, _cmd, response, _callback, _error| {
tx.send(response).unwrap();
}),
);
rx.recv().unwrap();
let res = rx.recv().expect("Failed to receive result from command");
match res {
InvokeResponse::Ok(b) => Ok(b),
InvokeResponse::Err(InvokeError(v)) => Err(v),
}
}
#[cfg(test)]