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 26f44b3bc1.

* cleanup setup_content logic

* add proc macros

* add underscore

* add proptest_config
This commit is contained in:
Tensor-Programming 2020-01-07 02:00:41 -05:00 committed by GitHub
parent 2ab604afd2
commit 7ac442e3f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 483 additions and 109 deletions

View File

@ -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)
}
}
}
}

View File

@ -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),
}
}
}
}

View File

@ -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]+") {

View File

@ -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)