mirror of
https://github.com/tauri-apps/tauri.git
synced 2025-01-01 23:42:33 +03:00
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:
parent
2ab604afd2
commit
7ac442e3f6
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
tauri/test/fixture/src-tauri/tauri.js
Normal file
219
tauri/test/fixture/src-tauri/tauri.js
Normal 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)
|
Loading…
Reference in New Issue
Block a user