diff --git a/gui/src/app/state/receive.rs b/gui/src/app/state/receive.rs index 7c7e431ae..4795b9186 100644 --- a/gui/src/app/state/receive.rs +++ b/gui/src/app/state/receive.rs @@ -350,15 +350,16 @@ mod tests { client::{Lianad, Request}, model::*, }, - utils::{mock::Daemon, sandbox::Sandbox}, + utils::{ + mock::{mock_wallet, Daemon}, + sandbox::Sandbox, + }, }; - use liana::{descriptors::LianaDescriptor, miniscript::bitcoin::Address}; + use liana::miniscript::bitcoin::Address; use serde_json::json; use std::str::FromStr; - const DESC: &str = "wsh(or_d(multi(2,[ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/<0;1>/*,[de6eb005/48'/1'/0'/2']tpubDFGuYfS2JwiUSEXiQuNGdT3R7WTDhbaE6jbUhgYSSdhmfQcSx7ZntMPPv7nrkvAqjpj3jX9wbhSGMeKVao4qAzhbNyBi7iQmv5xxQk6H6jz/<0;1>/*),and_v(v:pkh([ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/<2;3>/*),older(3))))#p9ax3xxp"; - #[tokio::test] async fn test_receive_panel() { let addr = @@ -372,7 +373,7 @@ mod tests { ChildNumber::from_normal_idx(0).unwrap() ))), )]); - let wallet = Arc::new(Wallet::new(LianaDescriptor::from_str(DESC).unwrap())); + let wallet = Arc::new(mock_wallet()); let sandbox: Sandbox = Sandbox::new(ReceivePanel::new(PathBuf::new(), wallet.clone())); let client = Arc::new(Lianad::new(daemon.run())); diff --git a/gui/src/app/state/transactions.rs b/gui/src/app/state/transactions.rs index 012159a35..177b69ddb 100644 --- a/gui/src/app/state/transactions.rs +++ b/gui/src/app/state/transactions.rs @@ -2,7 +2,6 @@ use std::{ collections::{HashMap, HashSet}, convert::TryInto, sync::Arc, - time::{SystemTime, UNIX_EPOCH}, }; use iced::Command; @@ -25,6 +24,7 @@ use crate::{ wallet::Wallet, }, daemon::model, + time, }; use crate::daemon::{ @@ -179,7 +179,12 @@ impl State for TransactionsPanel { self.pending_txs .iter_mut() .map(|tx| tx as &mut dyn Labelled) - .chain(self.txs.iter_mut().map(|tx| tx as &mut dyn Labelled)), + .chain(self.txs.iter_mut().map(|tx| tx as &mut dyn Labelled)) + .chain( + self.selected_tx + .iter_mut() + .map(|tx| tx as &mut dyn Labelled), + ), ) { Ok(cmd) => { return cmd; @@ -244,12 +249,7 @@ impl State for TransactionsPanel { let daemon1 = daemon.clone(); let daemon2 = daemon.clone(); let daemon3 = daemon.clone(); - let now: u32 = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs() - .try_into() - .unwrap(); + Command::batch(vec![ Command::perform( async move { daemon3.list_pending_txs().map_err(|e| e.into()) }, @@ -257,6 +257,10 @@ impl State for TransactionsPanel { ), Command::perform( async move { + let now: u32 = time::now() + .timestamp() + .try_into() + .expect("i64 conversion to u32 for timestamp"); daemon1 .list_history_txs(0, now, view::home::HISTORY_EVENT_PAGE_SIZE) .map_err(|e| e.into()) @@ -455,3 +459,58 @@ async fn rbf( daemon.update_spend_tx(&psbt)?; Ok(psbt.unsigned_tx.txid()) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + app::cache::Cache, + daemon::{ + client::{Lianad, Request}, + model::*, + }, + utils::{ + mock::{mock_descriptor, mock_wallet, Daemon}, + sandbox::Sandbox, + }, + }; + + use liana::miniscript::bitcoin::Network; + + use serde_json::json; + + #[tokio::test] + async fn test_transactions_panel() { + let daemon = Daemon::new(vec![ + ( + Some(json!({"method": "getinfo", "params": Option::::None})), + Ok(json!(GetInfoResult { + version: "".to_string(), + network: Network::Testnet, + block_height: 1, + sync: 1.0, + rescan_progress: None, + descriptors: GetInfoDescriptors { + main: mock_descriptor() + }, + timestamp: 1, + })), + ), + ( + Some(json!({"method": "listcoins", "params": Option::::None})), + Ok(json!(ListCoinsResult { coins: Vec::new() })), + ), + ( + Some(json!({"method": "listcoins", "params": Option::::None})), + Ok(json!(ListCoinsResult { coins: Vec::new() })), + ), + ]); + let wallet = Arc::new(mock_wallet()); + let sandbox: Sandbox = + Sandbox::new(TransactionsPanel::new(wallet.clone())); + let client = Arc::new(Lianad::new(daemon.run())); + let sandbox = sandbox.load(client, &Cache::default(), wallet).await; + + let _panel = sandbox.state(); + } +} diff --git a/gui/src/daemon/mod.rs b/gui/src/daemon/mod.rs index 5edbc9b09..2b1da64dd 100644 --- a/gui/src/daemon/mod.rs +++ b/gui/src/daemon/mod.rs @@ -239,6 +239,10 @@ pub trait Daemon: Debug { } } + if txids.is_empty() { + return Ok(Vec::new()); + } + let txs = self.list_txs(&txids)?.transactions; let mut txs = txs .into_iter() diff --git a/gui/src/daemon/model.rs b/gui/src/daemon/model.rs index e352b0db1..6148feac8 100644 --- a/gui/src/daemon/model.rs +++ b/gui/src/daemon/model.rs @@ -3,9 +3,9 @@ use std::collections::{HashMap, HashSet}; use liana::descriptors::LianaDescriptor; pub use liana::{ commands::{ - CreateSpendResult, GetAddressResult, GetInfoResult, GetLabelsResult, LabelItem, - ListCoinsEntry, ListCoinsResult, ListSpendEntry, ListSpendResult, ListTransactionsResult, - TransactionInfo, + CreateSpendResult, GetAddressResult, GetInfoDescriptors, GetInfoResult, GetLabelsResult, + LabelItem, ListCoinsEntry, ListCoinsResult, ListSpendEntry, ListSpendResult, + ListTransactionsResult, TransactionInfo, }, descriptors::{LianaPolicy, PartialSpendInfo, PathSpendInfo}, miniscript::bitcoin::{ diff --git a/gui/src/lib.rs b/gui/src/lib.rs index c064809d4..2a3ea8dfa 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -8,6 +8,7 @@ pub mod launcher; pub mod loader; pub mod logger; pub mod signer; +pub mod time; pub mod utils; use liana::Version; diff --git a/gui/src/time.rs b/gui/src/time.rs new file mode 100644 index 000000000..f923a7510 --- /dev/null +++ b/gui/src/time.rs @@ -0,0 +1,31 @@ +use chrono::prelude::*; + +#[cfg(test)] +pub mod mock_time { + use super::*; + use std::cell::RefCell; + + thread_local! { + static MOCK_TIME: RefCell>> = RefCell::new(None); + } + + pub fn now() -> DateTime { + MOCK_TIME.with(|cell| cell.borrow().as_ref().cloned().unwrap_or_else(Utc::now)) + } + + pub fn set_mock_time(time: DateTime) { + MOCK_TIME.with(|cell| *cell.borrow_mut() = Some(time)); + } + + pub fn clear_mock_time() { + MOCK_TIME.with(|cell| *cell.borrow_mut() = None); + } +} + +#[cfg(test)] +pub use mock_time::now; + +#[cfg(not(test))] +pub fn now() -> DateTime { + Utc::now() +} diff --git a/gui/src/utils/mock.rs b/gui/src/utils/mock.rs index 891a76773..2410c9023 100644 --- a/gui/src/utils/mock.rs +++ b/gui/src/utils/mock.rs @@ -1,13 +1,18 @@ -use crate::daemon::{client::Client, DaemonError}; -use serde::{de::DeserializeOwned, Serialize}; -use serde_json::{json, Value}; use std::fmt::Debug; +use std::str::FromStr; use std::sync::{ mpsc::{channel, Receiver, Sender}, Mutex, }; use std::thread; +use liana::descriptors::LianaDescriptor; + +use crate::app::wallet::Wallet; +use crate::daemon::{client::Client, DaemonError}; +use serde::{de::DeserializeOwned, Serialize}; +use serde_json::{json, Value}; + type TransportReceiver = Receiver>; #[derive(Debug)] @@ -75,3 +80,13 @@ impl Daemon { } } } + +const DESC: &str = "wsh(or_d(multi(2,[ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/<0;1>/*,[de6eb005/48'/1'/0'/2']tpubDFGuYfS2JwiUSEXiQuNGdT3R7WTDhbaE6jbUhgYSSdhmfQcSx7ZntMPPv7nrkvAqjpj3jX9wbhSGMeKVao4qAzhbNyBi7iQmv5xxQk6H6jz/<0;1>/*),and_v(v:pkh([ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/<2;3>/*),older(3))))#p9ax3xxp"; + +pub fn mock_wallet() -> Wallet { + Wallet::new(mock_descriptor()) +} + +pub fn mock_descriptor() -> LianaDescriptor { + LianaDescriptor::from_str(DESC).unwrap() +}