Bladeren bron

basic setup for command tests

Lucas Nogueira 2 jaren geleden
bovenliggende
commit
a2724b3e1c

+ 85 - 6
core/tauri-codegen/src/context.rs

@@ -5,8 +5,8 @@
 use std::path::{Path, PathBuf};
 use std::{ffi::OsStr, str::FromStr};
 
-use proc_macro2::TokenStream;
-use quote::quote;
+use proc_macro2::{Group, TokenStream, TokenTree};
+use quote::{quote, TokenStreamExt};
 use sha2::{Digest, Sha256};
 
 use tauri_utils::assets::AssetKey;
@@ -352,7 +352,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
 
     let info_plist_path = out_path.display().to_string();
     quote!({
-      tauri::embed_plist::embed_info_plist!(#info_plist_path);
+      #root::embed_plist::embed_info_plist!(#info_plist_path);
     })
   } else {
     quote!(())
@@ -399,7 +399,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
         assets: ::std::sync::Arc::new(#assets),
         schema: #schema.into(),
         key: #key.into(),
-        crypto_keys: std::boxed::Box::new(::tauri::utils::pattern::isolation::Keys::new().expect("unable to generate cryptographically secure keys for Tauri \"Isolation\" Pattern")),
+        crypto_keys: std::boxed::Box::new(#root::utils::pattern::isolation::Keys::new().expect("unable to generate cryptographically secure keys for Tauri \"Isolation\" Pattern")),
       })
     }
   };
@@ -438,7 +438,70 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
   #[cfg(not(feature = "shell-scope"))]
   let shell_scope_config = quote!();
 
-  Ok(quote!(#root::Context::new(
+  fn compare_token_stream(a: TokenStream, b: TokenStream) -> bool {
+    if a.clone().into_iter().count() != b.clone().into_iter().count() {
+      return false;
+    }
+    for (a, b) in a.into_iter().zip(b) {
+      if !compare_token_tree(a, b) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  fn compare_token_tree(a: TokenTree, b: TokenTree) -> bool {
+    match (a, b) {
+      (TokenTree::Group(a), TokenTree::Group(b)) => compare_token_stream(a.stream(), b.stream()),
+      (TokenTree::Ident(a), TokenTree::Ident(b)) => a.to_string() == b.to_string(),
+      (TokenTree::Punct(a), TokenTree::Punct(b)) => a.to_string() == b.to_string(),
+      (TokenTree::Literal(a), TokenTree::Literal(b)) => a.to_string() == b.to_string(),
+      _ => false,
+    }
+  }
+
+  fn change_tree_tauri_root(
+    token: TokenTree,
+    previous: &Option<TokenTree>,
+    new: &mut TokenStream,
+  ) -> bool {
+    match token {
+      TokenTree::Ident(i) if i == "utils" => {
+        new.append_all(quote!(tauri_utils));
+        false
+      }
+      TokenTree::Ident(i) => {
+        let ignore = match previous {
+          Some(TokenTree::Punct(p)) if p.as_char() == ':' => i == "tauri",
+          _ => false,
+        };
+        if !ignore {
+          new.append(i);
+        }
+        ignore
+      }
+      TokenTree::Group(g) => {
+        let mut stream = TokenStream::new();
+        let mut ignore = false;
+        let mut previous_token = None;
+        for token in g.stream() {
+          if ignore && matches!(token, TokenTree::Punct(_)) {
+            continue;
+          }
+          ignore = change_tree_tauri_root(token.clone(), &previous_token, &mut stream);
+          previous_token.replace(token);
+        }
+        new.append(Group::new(g.delimiter(), stream));
+        false
+      }
+      _ => {
+        new.append(token);
+        false
+      }
+    }
+  }
+
+  let context = quote!(#root::Context::new(
     #config,
     ::std::sync::Arc::new(#assets),
     #default_window_icon,
@@ -448,7 +511,23 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
     #info_plist,
     #pattern,
     #shell_scope_config
-  )))
+  ));
+
+  if compare_token_stream(root.clone(), quote!(crate)) {
+    let mut stream = TokenStream::new();
+    let mut ignore = false;
+    let mut previous_token = None;
+    for token in context {
+      if ignore && matches!(token, TokenTree::Punct(_)) {
+        continue;
+      }
+      ignore = change_tree_tauri_root(token.clone(), &previous_token, &mut stream);
+      previous_token.replace(token);
+    }
+    Ok(stream)
+  } else {
+    Ok(context)
+  }
 }
 
 fn ico_icon<P: AsRef<Path>>(

+ 39 - 19
core/tauri-macros/src/command/wrapper.rs

@@ -4,17 +4,18 @@
 
 use heck::{ToLowerCamelCase, ToSnakeCase};
 use proc_macro::TokenStream;
-use proc_macro2::TokenStream as TokenStream2;
+use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
 use quote::{format_ident, quote};
 use syn::{
   ext::IdentExt,
   parse::{Parse, ParseStream},
   parse_macro_input,
   spanned::Spanned,
-  FnArg, Ident, ItemFn, Lit, Meta, Pat, Token, Visibility,
+  FnArg, ItemFn, Lit, Meta, Pat, Token, Visibility,
 };
 
 struct WrapperAttributes {
+  root: TokenStream2,
   execution_context: ExecutionContext,
   argument_case: ArgumentCase,
 }
@@ -22,6 +23,7 @@ struct WrapperAttributes {
 impl Parse for WrapperAttributes {
   fn parse(input: ParseStream) -> syn::Result<Self> {
     let mut wrapper_attributes = WrapperAttributes {
+      root: quote!(::tauri),
       execution_context: ExecutionContext::Blocking,
       argument_case: ArgumentCase::Camel,
     };
@@ -43,6 +45,11 @@ impl Parse for WrapperAttributes {
                 }
               };
             }
+          } else if v.path.is_ident("root") {
+            if let Lit::Str(s) = v.lit {
+              let ident = Ident::new(&s.value(), Span::call_site());
+              wrapper_attributes.root = quote!(#ident);
+            }
           }
         }
         Ok(Meta::Path(p)) => {
@@ -104,21 +111,28 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
   };
 
   // body to the command wrapper or a `compile_error!` of an error occurred while parsing it.
-  let body = syn::parse::<WrapperAttributes>(attributes)
+  let (body, attributes) = syn::parse::<WrapperAttributes>(attributes)
     .map(|mut attrs| {
       if function.sig.asyncness.is_some() {
         attrs.execution_context = ExecutionContext::Async;
       }
       attrs
     })
-    .and_then(|attrs| match attrs.execution_context {
-      ExecutionContext::Async => body_async(&function, &invoke, attrs.argument_case),
-      ExecutionContext::Blocking => body_blocking(&function, &invoke, attrs.argument_case),
+    .and_then(|attrs| {
+      let body = match attrs.execution_context {
+        ExecutionContext::Async => body_async(&function, &invoke, &attrs),
+        ExecutionContext::Blocking => body_blocking(&function, &invoke, &attrs),
+      };
+      body.map(|b| (b, Some(attrs)))
     })
-    .unwrap_or_else(syn::Error::into_compile_error);
+    .unwrap_or_else(|e| (syn::Error::into_compile_error(e), None));
 
   let Invoke { message, resolver } = invoke;
 
+  let root = attributes
+    .map(|a| a.root)
+    .unwrap_or_else(|| quote!(::tauri));
+
   // Rely on rust 2018 edition to allow importing a macro from a path.
   quote!(
     #function
@@ -129,10 +143,10 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
         // double braces because the item is expected to be a block expression
         ($path:path, $invoke:ident) => {{
           #[allow(unused_imports)]
-          use ::tauri::command::private::*;
+          use #root::command::private::*;
           // prevent warnings when the body is a `compile_error!` or if the command has no arguments
           #[allow(unused_variables)]
-          let ::tauri::Invoke { message: #message, resolver: #resolver } = $invoke;
+          let #root::Invoke { message: #message, resolver: #resolver } = $invoke;
 
           #body
       }};
@@ -150,9 +164,13 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
 /// See the [`tauri::command`] module for all the items and traits that make this possible.
 ///
 /// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html
-fn body_async(function: &ItemFn, invoke: &Invoke, case: ArgumentCase) -> syn::Result<TokenStream2> {
+fn body_async(
+  function: &ItemFn,
+  invoke: &Invoke,
+  attributes: &WrapperAttributes,
+) -> syn::Result<TokenStream2> {
   let Invoke { message, resolver } = invoke;
-  parse_args(function, message, case).map(|args| {
+  parse_args(function, message, attributes).map(|args| {
     quote! {
       #resolver.respond_async_serialized(async move {
         let result = $path(#(#args?),*);
@@ -171,10 +189,10 @@ fn body_async(function: &ItemFn, invoke: &Invoke, case: ArgumentCase) -> syn::Re
 fn body_blocking(
   function: &ItemFn,
   invoke: &Invoke,
-  case: ArgumentCase,
+  attributes: &WrapperAttributes,
 ) -> syn::Result<TokenStream2> {
   let Invoke { message, resolver } = invoke;
-  let args = parse_args(function, message, case)?;
+  let args = parse_args(function, message, attributes)?;
 
   // the body of a `match` to early return any argument that wasn't successful in parsing.
   let match_body = quote!({
@@ -193,13 +211,13 @@ fn body_blocking(
 fn parse_args(
   function: &ItemFn,
   message: &Ident,
-  case: ArgumentCase,
+  attributes: &WrapperAttributes,
 ) -> syn::Result<Vec<TokenStream2>> {
   function
     .sig
     .inputs
     .iter()
-    .map(|arg| parse_arg(&function.sig.ident, arg, message, case))
+    .map(|arg| parse_arg(&function.sig.ident, arg, message, attributes))
     .collect()
 }
 
@@ -208,7 +226,7 @@ fn parse_arg(
   command: &Ident,
   arg: &FnArg,
   message: &Ident,
-  case: ArgumentCase,
+  attributes: &WrapperAttributes,
 ) -> syn::Result<TokenStream2> {
   // we have no use for self arguments
   let mut arg = match arg {
@@ -243,7 +261,7 @@ fn parse_arg(
     ));
   }
 
-  match case {
+  match attributes.argument_case {
     ArgumentCase::Camel => {
       key = key.to_lower_camel_case();
     }
@@ -252,8 +270,10 @@ fn parse_arg(
     }
   }
 
-  Ok(quote!(::tauri::command::CommandArg::from_command(
-    ::tauri::command::CommandItem {
+  let root = &attributes.root;
+
+  Ok(quote!(#root::command::CommandArg::from_command(
+    #root::command::CommandItem {
       name: stringify!(#command),
       key: #key,
       message: &#message,

+ 0 - 1
core/tauri/Cargo.toml

@@ -119,7 +119,6 @@ quickcheck = "1.0.3"
 quickcheck_macros = "1.0.0"
 serde = { version = "1.0", features = [ "derive" ] }
 serde_json = "1.0"
-tauri = { path = ".", default-features = false, features = [ "wry" ] }
 tokio-test = "0.4.2"
 tokio = { version = "1", features = [ "full" ] }
 cargo_toml = "0.11"

+ 4 - 0
core/tauri/src/test/mod.rs

@@ -85,6 +85,10 @@ pub fn mock_context<A: Assets>(assets: A) -> crate::Context<A> {
   }
 }
 
+pub fn mock_builder() -> crate::Builder<MockRuntime> {
+  crate::Builder::new()
+}
+
 pub fn mock_app() -> crate::App<MockRuntime> {
   crate::Builder::<MockRuntime>::new()
     .build(mock_context(noop_assets()))

+ 64 - 2
core/tauri/src/window.rs

@@ -1515,9 +1515,71 @@ impl<R: Runtime> Window<R> {
 
 #[cfg(test)]
 mod tests {
+  use std::sync::mpsc::{sync_channel, SyncSender};
+
+  use super::{Window, WindowBuilder};
+  use crate::{api::ipc::CallbackFn, test, InvokePayload, State};
+
   #[test]
   fn window_is_send_sync() {
-    crate::test_utils::assert_send::<super::Window>();
-    crate::test_utils::assert_sync::<super::Window>();
+    crate::test_utils::assert_send::<Window>();
+    crate::test_utils::assert_sync::<Window>();
+  }
+
+  #[test]
+  fn regular_commands() {
+    #[derive(Debug, Eq, PartialEq)]
+    enum Response {
+      Cmd,
+    }
+
+    #[crate::command(root = "crate")]
+    fn cmd() {
+      println!("cmd");
+    }
+
+    #[crate::command(root = "crate")]
+    fn cmd_state(channel: State<'_, Channel>) {
+      println!("cmd state");
+      channel.tx.send(Response::Cmd).unwrap();
+    }
+
+    struct Channel {
+      tx: SyncSender<Response>,
+    }
+
+    let (tx, rx) = sync_channel(1);
+    let channel = Channel { tx };
+
+    let app = test::mock_builder()
+      .manage(channel)
+      .invoke_handler(crate::generate_handler![cmd, cmd_state])
+      .build(test::mock_context(test::noop_assets()))
+      .unwrap();
+    let window = WindowBuilder::new(&app, "test", Default::default())
+      .build()
+      .unwrap();
+    window
+      .clone()
+      .on_message(InvokePayload {
+        cmd: "cmd".into(),
+        tauri_module: None,
+        callback: CallbackFn(0),
+        error: CallbackFn(0),
+        inner: Default::default(),
+      })
+      .unwrap();
+
+    window
+      .clone()
+      .on_message(InvokePayload {
+        cmd: "cmd_state".into(),
+        tauri_module: None,
+        callback: CallbackFn(0),
+        error: CallbackFn(0),
+        inner: Default::default(),
+      })
+      .unwrap();
+    assert_eq!(rx.try_recv().unwrap(), Response::Cmd);
   }
 }