Sfoglia il codice sorgente

feat(core) add testing and refactor (#281)

* update .gitignore

* remove cargo.lock

* refactor endpoints.

* remove webview from fns

* add testing

* stop proptest from opening 100 tabs

* add config

* proptest for listen_fn

* add tauri.js to fixture

* add simple tests to runner

* add setup_content unit test.

* add proptest for check_server_url

* add setup_port unit test.

* update tauriresult to work with tiny_http

* Revert "update tauriresult to work with tiny_http"

This reverts commit 26f44b3bc10acb196bfdd07f932204163b02ffd7.

* cleanup setup_content logic

* add proc macros

* add underscore

* add proptest_config
Tensor-Programming 5 anni fa
parent
commit
7ac442e3f6
4 ha cambiato i file con 483 aggiunte e 109 eliminazioni
  1. 77 0
      tauri/src/app/runner.rs
  2. 186 109
      tauri/src/endpoints.rs
  3. 1 0
      tauri/src/lib.rs
  4. 219 0
      tauri/test/fixture/src-tauri/tauri.js

+ 77 - 0
tauri/src/app/runner.rs

@@ -200,3 +200,80 @@ fn build_webview(
       .build()?,
   )
 }
+
+#[cfg(test)]
+mod test {
+  use proptest::prelude::*;
+  use web_view::Content;
+
+  #[cfg(not(feature = "embedded-server"))]
+  use std::{fs::read_to_string, path::Path};
+
+  fn init_config() -> crate::config::Config {
+    crate::config::get().expect("unable to setup default config")
+  }
+
+  #[test]
+  fn check_setup_content() {
+    let config = init_config();
+    let _c = config.clone();
+
+    let res = super::setup_content(config);
+
+    #[cfg(feature = "embedded-server")]
+    match res {
+      Ok(Content::Url(u)) => assert!(u.contains("http://")),
+      _ => assert!(false),
+    }
+
+    #[cfg(feature = "no-server")]
+    match res {
+      Ok(Content::Html(s)) => assert_eq!(
+        s,
+        read_to_string(Path::new(env!("TAURI_DIST_DIR")).join("index.tauri.html")).unwrap()
+      ),
+      _ => assert!(false),
+    }
+
+    #[cfg(not(any(feature = "embedded-server", feature = "no-server")))]
+    match res {
+      Ok(Content::Url(dp)) => assert_eq!(dp, _c.build.dev_path),
+      Ok(Content::Html(s)) => assert_eq!(
+        s,
+        read_to_string(Path::new(env!("TAURI_DIST_DIR")).join("index.tauri.html")).unwrap()
+      ),
+      _ => assert!(false),
+    }
+  }
+
+  #[cfg(feature = "embedded-server")]
+  #[test]
+  fn check_setup_port() {
+    let config = init_config();
+
+    let res = super::setup_port(config);
+    match res {
+      Some((_s, _b)) => assert!(true),
+      _ => assert!(false),
+    }
+  }
+
+  proptest! {
+    #![proptest_config(ProptestConfig::with_cases(10000))]
+    #[cfg(feature = "embedded-server")]
+    #[test]
+    fn check_server_url(port in (any::<u32>().prop_map(|v| v.to_string()))) {
+      let config = init_config();
+      let valid = true;
+
+      let p = port.clone();
+
+      let res = super::setup_server_url(config, valid, port);
+
+      match res {
+        Some(url) => assert!(url.contains(&p)),
+        None => assert!(false)
+      }
+    }
+  }
+}

+ 186 - 109
tauri/src/endpoints.rs

@@ -12,40 +12,7 @@ pub(crate) fn handle<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> Tau
     Ok(command) => {
       match command {
         Init {} => {
-          #[cfg(not(any(feature = "all-api", feature = "event")))]
-          let event_init = "";
-          #[cfg(any(feature = "all-api", feature = "event"))]
-          let event_init = format!(
-            "
-              window['{queue}'] = [];
-              window['{fn}'] = function (payload, salt, ignoreQueue) {{
-              const listeners = (window['{listeners}'] && window['{listeners}'][payload.type]) || []
-              if (!ignoreQueue && listeners.length === 0) {{
-                window['{queue}'].push({{
-                  payload: payload,
-                  salt: salt
-                }})
-              }}
-
-              if (listeners.length > 0) {{
-                window.tauri.promisified({{
-                  cmd: 'validateSalt',
-                  salt: salt
-                }}).then(function () {{
-                  for (let i = listeners.length - 1; i >= 0; i--) {{
-                    const listener = listeners[i]
-                    if (listener.once)
-                      listeners.splice(i, 1)
-                    listener.handler(payload)
-                  }}
-                }})
-              }}
-            }}
-            ",
-            fn = crate::event::emit_function_name(),
-            listeners = crate::event::event_listeners_object_name(),
-            queue = crate::event::event_queue_object_name()
-          );
+          let event_init = init()?;
           webview.eval(&format!(
             r#"{event_init}
                 window.external.invoke('{{"cmd":"__initialized"}}')
@@ -109,11 +76,8 @@ pub(crate) fn handle<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> Tau
         }
         #[cfg(any(feature = "all-api", feature = "open"))]
         Open { uri } => {
-          crate::spawn(move || {
-            webbrowser::open(&uri).expect("Failed to open webbrowser with uri");
-          });
+          open_fn(uri)?;
         }
-
         ValidateSalt {
           salt,
           callback,
@@ -127,33 +91,8 @@ pub(crate) fn handle<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> Tau
           handler,
           once,
         } => {
-          webview
-            .eval(&format!(
-              "
-                if (window['{listeners}'] === void 0) {{
-                  window['{listeners}'] = {{}}
-                 }}
-                if (window['{listeners}']['{evt}'] === void 0) {{
-                  window['{listeners}']['{evt}'] = []
-                }}
-                window['{listeners}']['{evt}'].push({{
-                  handler: window['{handler}'],
-                  once: {once_flag}
-                }});
-
-                for (let i = 0; i < (window['{queue}'] || []).length; i++) {{
-                  const e = window['{queue}'][i];
-                  window['{emit}'](e.payload, e.salt, true)
-                }}
-              ",
-              listeners = crate::event::event_listeners_object_name(),
-              queue = crate::event::event_queue_object_name(),
-              emit = crate::event::emit_function_name(),
-              evt = event,
-              handler = handler,
-              once_flag = if once { "true" } else { "false" }
-            ))
-            .expect("failed to call webview.eval from listen");
+          let js_string = listen_fn(event, handler, once)?;
+          webview.eval(&js_string)?;
         }
         #[cfg(any(feature = "all-api", feature = "event"))]
         Emit { event, payload } => {
@@ -166,53 +105,191 @@ pub(crate) fn handle<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> Tau
           callback,
           error,
         } => {
-          let handle = webview.handle();
-          crate::execute_promise(
-            webview,
-            move || {
-              let read_asset = crate::assets::ASSETS.get(&format!(
-                "{}{}{}",
-                env!("TAURI_DIST_DIR"),
-                if asset.starts_with("/") { "" } else { "/" },
-                asset
-              ));
-              if read_asset.is_err() {
-                return Err(r#""Asset not found""#.to_string());
-              }
-
-              if asset_type == "image" {
-                let ext = if asset.ends_with("gif") {
-                  "gif"
-                } else if asset.ends_with("png") {
-                  "png"
-                } else {
-                  "jpeg"
-                };
-                Ok(format!(
-                  "`data:image/{};base64,{}`",
-                  ext,
-                  base64::encode(&read_asset.expect("Failed to read asset type").into_owned())
-                ))
-              } else {
-                handle
-                  .dispatch(move |_webview| {
-                    _webview.eval(
-                      &std::str::from_utf8(
-                        &read_asset.expect("Failed to read asset type").into_owned(),
-                      )
-                      .expect("failed to convert asset bytes to u8 slice"),
-                    )
-                  })
-                  .map_err(|err| format!("`{}`", err))
-                  .map(|_| r#""Asset loaded successfully""#.to_string())
-              }
-            },
-            callback,
-            error,
-          );
+          load_asset(webview, asset, asset_type, callback, error)?;
         }
       }
       Ok(true)
     }
   }
 }
+
+fn init() -> TauriResult<String> {
+  #[cfg(not(any(feature = "all-api", feature = "event")))]
+  return Ok(String::from(""));
+  #[cfg(any(feature = "all-api", feature = "event"))]
+  return Ok(format!(
+            "
+              window['{queue}'] = [];
+              window['{fn}'] = function (payload, salt, ignoreQueue) {{
+              const listeners = (window['{listeners}'] && window['{listeners}'][payload.type]) || []
+              if (!ignoreQueue && listeners.length === 0) {{
+                window['{queue}'].push({{
+                  payload: payload,
+                  salt: salt
+                }})
+              }}
+
+              if (listeners.length > 0) {{
+                window.tauri.promisified({{
+                  cmd: 'validateSalt',
+                  salt: salt
+                }}).then(function () {{
+                  for (let i = listeners.length - 1; i >= 0; i--) {{
+                    const listener = listeners[i]
+                    if (listener.once)
+                      listeners.splice(i, 1)
+                    listener.handler(payload)
+                  }}
+                }})
+              }}
+            }}
+            ",
+            fn = crate::event::emit_function_name(),
+            queue = crate::event::event_listeners_object_name(),
+            listeners = crate::event::event_queue_object_name(),
+  ));
+}
+
+#[cfg(any(feature = "all-api", feature = "open"))]
+fn open_fn(uri: String) -> TauriResult<()> {
+  crate::spawn(move || {
+    #[cfg(test)]
+    assert!(uri.contains("http://"));
+
+    #[cfg(not(test))]
+    webbrowser::open(&uri).expect("Failed to open webbrowser with uri");
+  });
+
+  Ok(())
+}
+
+#[cfg(any(feature = "all-api", feature = "event"))]
+fn listen_fn(event: String, handler: String, once: bool) -> TauriResult<String> {
+  Ok(format!(
+    "if (window['{listeners}'] === void 0) {{
+      window['{listeners}'] = {{}}
+      }}
+    if (window['{listeners}']['{evt}'] === void 0) {{
+      window['{listeners}']['{evt}'] = []
+    }}
+    window['{listeners}']['{evt}'].push({{
+      handler: window['{handler}'],
+      once: {once_flag}
+    }});
+
+    for (let i = 0; i < (window['{queue}'] || []).length; i++) {{
+      const e = window['{queue}'][i];
+      window['{emit}'](e.payload, e.salt, true)
+    }}
+  ",
+    listeners = crate::event::event_listeners_object_name(),
+    queue = crate::event::event_queue_object_name(),
+    emit = crate::event::emit_function_name(),
+    evt = event,
+    handler = handler,
+    once_flag = if once { "true" } else { "false" }
+  ))
+}
+
+#[cfg(not(any(feature = "dev-server", feature = "embedded-server")))]
+fn load_asset<T: 'static>(
+  webview: &mut WebView<'_, T>,
+  asset: String,
+  asset_type: String,
+  callback: String,
+  error: String,
+) -> TauriResult<()> {
+  let handle = webview.handle();
+  crate::execute_promise(
+    webview,
+    move || {
+      let read_asset = crate::assets::ASSETS.get(&format!(
+        "{}{}{}",
+        env!("TAURI_DIST_DIR"),
+        if asset.starts_with("/") { "" } else { "/" },
+        asset
+      ));
+      if read_asset.is_err() {
+        return Err(r#""Asset not found""#.to_string());
+      }
+
+      if asset_type == "image" {
+        let ext = if asset.ends_with("gif") {
+          "gif"
+        } else if asset.ends_with("png") {
+          "png"
+        } else {
+          "jpeg"
+        };
+        Ok(format!(
+          "`data:image/{};base64,{}`",
+          ext,
+          base64::encode(&read_asset.expect("Failed to read asset type").into_owned())
+        ))
+      } else {
+        handle
+          .dispatch(move |_webview| {
+            _webview.eval(
+              &std::str::from_utf8(&read_asset.expect("Failed to read asset type").into_owned())
+                .expect("failed to convert asset bytes to u8 slice"),
+            )
+          })
+          .map_err(|err| format!("`{}`", err))
+          .map(|_| r#""Asset loaded successfully""#.to_string())
+      }
+    },
+    callback,
+    error,
+  );
+
+  Ok(())
+}
+
+#[cfg(test)]
+mod test {
+  use proptest::prelude::*;
+
+  #[test]
+  // test to see if check init produces a string or not.
+  fn check_init() {
+    if cfg!(not(any(feature = "all-api", feature = "event"))) {
+      let res = super::init();
+      match res {
+        Ok(s) => assert_eq!(s, ""),
+        Err(_) => assert!(false),
+      }
+    } else if cfg!(any(feature = "all-api", feature = "event")) {
+      let res = super::init();
+      match res {
+        Ok(s) => assert!(s.contains("window.tauri.promisified")),
+        Err(_) => assert!(false),
+      }
+    }
+  }
+
+  // check the listen_fn for various usecases.
+  proptest! {
+    #[cfg(any(feature = "all-api", feature = "event"))]
+    #[test]
+    fn check_listen_fn(event in "", handler in "", once in proptest::bool::ANY) {
+      let res = super::listen_fn(event, handler, once);
+      match res {
+        Ok(_) => assert!(true),
+        Err(_) => assert!(false)
+      }
+    }
+  }
+
+  // Test the open func to see if proper uris can be opened by the browser.
+  proptest! {
+    #[cfg(any(feature = "all-api", feature = "open"))]
+    #[test]
+    fn check_open(uri in r"(http://)([\\w\\d\\.]+([\\w]{2,6})?)") {
+      let res = super::open_fn(uri);
+      match res {
+        Ok(_) => assert!(true),
+        Err(_) => assert!(false),
+    }
+  }
+  }
+}

+ 1 - 0
tauri/src/lib.rs

@@ -87,6 +87,7 @@ mod test {
   use proptest::prelude::*;
 
   proptest! {
+    #![proptest_config(ProptestConfig::with_cases(10000))]
     #[test]
     // check to see if spawn executes a function.
     fn check_spawn_task(task in "[a-z]+") {

+ 219 - 0
tauri/test/fixture/src-tauri/tauri.js

@@ -0,0 +1,219 @@
+/* eslint-disable */
+
+/**
+ *  * THIS FILE IS GENERATED AUTOMATICALLY.
+ * DO NOT EDIT.
+ *
+ * Please whitelist these API functions in tauri.conf.json
+ *
+ **/
+
+/**
+ * @module tauri
+ * @description This API interface makes powerful interactions available
+ * to be run on client side applications. They are opt-in features, and
+ * must be enabled in tauri.conf.json
+ *
+ * Each binding MUST provide these interfaces in order to be compliant,
+ * and also whitelist them based upon the developer's settings.
+ */
+
+function s4() {
+  return Math.floor((1 + Math.random()) * 0x10000)
+    .toString(16)
+    .substring(1)
+}
+
+var uid = function () {
+  return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+    s4() + '-' + s4() + s4() + s4()
+}
+
+function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
+
+function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+
+function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
+
+
+
+
+var __reject = function () {
+  return new Promise(function (_, reject) {
+    reject();
+  });
+}
+
+window.tauri = {
+  
+  invoke: function invoke(args) {
+    window.external.invoke(JSON.stringify(args));
+  },
+
+  
+  listen: function listen(event, handler) {
+    
+      
+        return __reject()
+    
+  },
+
+  
+  emit: function emit(evt, payload) {
+    
+      
+        return __reject()
+    
+  },
+
+  
+  transformCallback: function transformCallback(callback) {
+    var once = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
+    var identifier = Object.freeze(uid());
+
+    window[identifier] = function (result) {
+      if (once) {
+        delete window[identifier];
+      }
+
+      return callback && callback(result);
+    };
+
+    return identifier;
+  },
+
+  
+  promisified: function promisified(args) {
+    var _this = this;
+
+    return new Promise(function (resolve, reject) {
+      _this.invoke(_objectSpread({
+        callback: _this.transformCallback(resolve),
+        error: _this.transformCallback(reject)
+      }, args));
+    });
+  },
+
+  
+  readTextFile: function readTextFile(path) {
+    
+      
+        return __reject()
+    
+  },
+
+  
+  readBinaryFile: function readBinaryFile(path) {
+    
+      
+        return __reject()
+    
+  },
+
+  
+  writeFile: function writeFile(cfg) {
+    
+      
+        return __reject()
+    
+  },
+
+  
+  listFiles: function listFiles(path) {
+    
+      
+        return __reject()
+    
+  },
+
+  
+  listDirs: function listDirs(path) {
+    
+      
+        return __reject()
+    
+  },
+
+  
+  setTitle: function setTitle(title) {
+    
+      
+    return __reject()
+    
+  },
+
+  
+  open: function open(uri) {
+    
+    
+    return __reject()
+      
+  },
+
+  
+  execute: function execute(command, args) {
+    
+      
+        return __reject()
+    
+  },
+
+  bridge: function bridge(command, payload) {
+    
+      
+            return __reject()
+    
+  },
+
+  loadAsset: function loadAsset(assetName, assetType) {
+    return this.promisified({
+      cmd: 'loadAsset',
+      asset: assetName,
+      asset_type: assetType || 'unknown'
+    })
+  }
+};
+
+// init tauri API
+try {
+  window.tauri.invoke({
+    cmd: 'init'
+  })
+} catch (e) {
+  window.addEventListener('DOMContentLoaded', function () {
+    window.tauri.invoke({
+      cmd: 'init'
+    })
+  }, true)
+}
+
+document.addEventListener('error', function (e) {
+  var target = e.target
+  while (target != null) {
+    if (target.matches ? target.matches('img') : target.msMatchesSelector('img')) {
+      window.tauri.loadAsset(target.src, 'image')
+        .then(img => {
+          target.src = img
+        })
+      break
+    }
+    target = target.parentElement
+  }
+}, true)
+
+window.addEventListener('DOMContentLoaded', function () {
+  // open <a href="..."> links with the Tauri API
+  document.querySelector('body').addEventListener('click', function (e) {
+    var target = e.target
+    while (target != null) {
+      if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
+        window.tauri.open(target.href)
+        break
+      }
+      target = target.parentElement
+    }
+  }, true)
+}, true)