summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-03-03 16:35:04 +0100
committerDavid Lönnhager <david.l@mullvad.net>2025-03-05 23:32:49 +0100
commitdf0dd166e5aceb29ecb1ae7460972f715db0d8c5 (patch)
tree7394ff41aca7c656f135035d92e2ee755e1c71b4
parentb9ce64d1e7d27e4119eafb15b17a1b4bfdc7404e (diff)
downloadmullvadvpn-df0dd166e5aceb29ecb1ae7460972f715db0d8c5.tar.xz
mullvadvpn-df0dd166e5aceb29ecb1ae7460972f715db0d8c5.zip
Move fake implementations of updater/downloader traits to own module
-rw-r--r--installer-downloader/tests/controller.rs368
-rw-r--r--installer-downloader/tests/mock.rs365
2 files changed, 370 insertions, 363 deletions
diff --git a/installer-downloader/tests/controller.rs b/installer-downloader/tests/controller.rs
index 6eb9e727d0..dcc571ea72 100644
--- a/installer-downloader/tests/controller.rs
+++ b/installer-downloader/tests/controller.rs
@@ -8,371 +8,13 @@
use insta::assert_yaml_snapshot;
use installer_downloader::controller::AppController;
-use installer_downloader::delegate::{AppDelegate, AppDelegateQueue, ErrorMessage};
-use installer_downloader::environment::{Architecture, Environment};
-use installer_downloader::temp::DirectoryProvider;
-use installer_downloader::ui_downloader::UiAppDownloaderParameters;
-use mullvad_update::api::VersionInfoProvider;
-use mullvad_update::app::{AppDownloader, DownloadError};
-use mullvad_update::fetch::ProgressUpdater;
-use mullvad_update::version::{Version, VersionInfo, VersionParameters};
-use std::io;
-use std::path::{Path, PathBuf};
-use std::sync::{Arc, LazyLock, Mutex};
-use std::time::Duration;
-use std::vec::Vec;
-
-pub struct FakeVersionInfoProvider {}
-
-static FAKE_VERSION: LazyLock<VersionInfo> = LazyLock::new(|| VersionInfo {
- stable: Version {
- version: "2025.1".parse().unwrap(),
- urls: vec!["https://mullvad.net/fakeapp".to_owned()],
- size: 1234,
- changelog: "a changelog".to_owned(),
- sha256: [0u8; 32],
- },
- beta: None,
-});
-
-const FAKE_ENVIRONMENT: Environment = Environment {
- architecture: Architecture::X86,
+use mock::{
+ FakeAppDelegate, FakeAppDownloaderHappyPath, FakeAppDownloaderVerifyFail,
+ FakeDirectoryProvider, FakeVersionInfoProvider, FAKE_ENVIRONMENT,
};
+use std::time::Duration;
-#[async_trait::async_trait]
-impl VersionInfoProvider for FakeVersionInfoProvider {
- async fn get_version_info(&self, _params: VersionParameters) -> anyhow::Result<VersionInfo> {
- Ok(FAKE_VERSION.clone())
- }
-}
-
-pub struct FakeDirectoryProvider<const SUCCEED: bool> {}
-
-#[async_trait::async_trait]
-impl<const SUCCEEDED: bool> DirectoryProvider for FakeDirectoryProvider<SUCCEEDED> {
- async fn create_download_dir() -> anyhow::Result<PathBuf> {
- if SUCCEEDED {
- Ok(Path::new("/tmp/fake").to_owned())
- } else {
- anyhow::bail!("Failed to create directory");
- }
- }
-}
-
-/// Downloader for which all steps immediately succeed
-pub type FakeAppDownloaderHappyPath = FakeAppDownloader<true, true, true>;
-
-/// Downloader for which the download step fails
-pub type FakeAppDownloaderDownloadFail = FakeAppDownloader<false, false, false>;
-
-/// Downloader for which the verification step fails
-pub type FakeAppDownloaderVerifyFail = FakeAppDownloader<true, false, false>;
-
-impl<const A: bool, const B: bool, const C: bool> From<UiAppDownloaderParameters<FakeAppDelegate>>
- for FakeAppDownloader<A, B, C>
-{
- fn from(params: UiAppDownloaderParameters<FakeAppDelegate>) -> Self {
- FakeAppDownloader { params }
- }
-}
-
-/// Fake app downloader
-///
-/// Parameters:
-/// * EXE_SUCCEED - whether fetching the binary succeeds
-/// * VERIFY_SUCCEED - whether verifying the binary succeeds
-/// * LAUNCH_SUCCEED - whether launching the binary succeeds
-pub struct FakeAppDownloader<
- const EXE_SUCCEED: bool,
- const VERIFY_SUCCEED: bool,
- const LAUNCH_SUCCEED: bool,
-> {
- params: UiAppDownloaderParameters<FakeAppDelegate>,
-}
-
-#[async_trait::async_trait]
-impl<const EXE_SUCCEED: bool, const VERIFY_SUCCEED: bool, const LAUNCH_SUCCEED: bool> AppDownloader
- for FakeAppDownloader<EXE_SUCCEED, VERIFY_SUCCEED, LAUNCH_SUCCEED>
-{
- async fn download_executable(&mut self) -> Result<(), DownloadError> {
- self.params.app_progress.set_url(&self.params.app_url);
- self.params.app_progress.clear_progress();
- if EXE_SUCCEED {
- self.params.app_progress.set_progress(1.);
- Ok(())
- } else {
- Err(DownloadError::FetchApp(anyhow::anyhow!(
- "fetching app failed"
- )))
- }
- }
-
- async fn verify(&mut self) -> Result<(), DownloadError> {
- if VERIFY_SUCCEED {
- Ok(())
- } else {
- Err(DownloadError::Verification(anyhow::anyhow!(
- "verification failed"
- )))
- }
- }
-
- async fn install(&mut self) -> Result<(), DownloadError> {
- if LAUNCH_SUCCEED {
- Ok(())
- } else {
- Err(DownloadError::InstallFailed(io::Error::other(
- "install failed",
- )))
- }
- }
-}
-
-/// A fake queue that stores callbacks so that tests can run them later.
-#[derive(Clone, Default)]
-pub struct FakeQueue {
- callbacks: Arc<Mutex<Vec<MainThreadCallback>>>,
-}
-
-pub type MainThreadCallback = Box<dyn FnOnce(&mut FakeAppDelegate) + Send>;
-
-impl FakeQueue {
- /// Run all queued callbacks on the given delegate.
- fn run_callbacks(&self, delegate: &mut FakeAppDelegate) {
- let mut callbacks = self.callbacks.lock().unwrap();
- for cb in callbacks.drain(..) {
- cb(delegate);
- }
- }
-}
-
-impl AppDelegateQueue<FakeAppDelegate> for FakeQueue {
- fn queue_main<F: FnOnce(&mut FakeAppDelegate) + 'static + Send>(&self, callback: F) {
- self.callbacks.lock().unwrap().push(Box::new(callback));
- }
-}
-
-/// A fake [AppDelegate]
-#[derive(Default)]
-pub struct FakeAppDelegate {
- /// Callback registered by `on_download`
- pub download_callback: Option<Box<dyn Fn() + Send>>,
- /// Callback registered by `on_cancel`
- pub cancel_callback: Option<Box<dyn Fn() + Send>>,
- /// Callback registered by `on_beta_link`
- pub beta_callback: Option<Box<dyn Fn() + Send>>,
- /// Callback registered by `on_stable_link`
- pub stable_callback: Option<Box<dyn Fn() + Send>>,
- /// Callback registered by `on_error_cancel`
- pub error_cancel_callback: Option<Box<dyn Fn() + Send>>,
- /// Callback registered by `on_error_retry`
- pub error_retry_callback: Option<Box<dyn Fn() + Send>>,
- /// State of delegate
- pub state: DelegateState,
- /// Queue used to simulate the main thread
- pub queue: FakeQueue,
-}
-
-/// A complete state of the UI, including its call history
-#[derive(Default, serde::Serialize)]
-pub struct DelegateState {
- pub status_text: String,
- pub download_text: String,
- pub download_button_visible: bool,
- pub cancel_button_visible: bool,
- pub cancel_button_enabled: bool,
- pub download_button_enabled: bool,
- pub download_progress: u32,
- pub download_progress_visible: bool,
- pub beta_text_visible: bool,
- pub stable_text_visible: bool,
- pub error_message_visible: bool,
- pub error_message: ErrorMessage,
- pub quit: bool,
- /// Record of method calls.
- pub call_log: Vec<String>,
-}
-
-impl AppDelegate for FakeAppDelegate {
- type Queue = FakeQueue;
-
- fn on_download<F>(&mut self, callback: F)
- where
- F: Fn() + Send + 'static,
- {
- self.state.call_log.push("on_download".into());
- self.download_callback = Some(Box::new(callback));
- }
-
- fn on_cancel<F>(&mut self, callback: F)
- where
- F: Fn() + Send + 'static,
- {
- self.state.call_log.push("on_cancel".into());
- self.cancel_callback = Some(Box::new(callback));
- }
-
- fn on_beta_link<F>(&mut self, callback: F)
- where
- F: Fn() + Send + 'static,
- {
- self.state.call_log.push("on_beta_link".into());
- self.beta_callback = Some(Box::new(callback));
- }
-
- fn on_stable_link<F>(&mut self, callback: F)
- where
- F: Fn() + Send + 'static,
- {
- self.state.call_log.push("on_stable_link".into());
- self.stable_callback = Some(Box::new(callback));
- }
-
- fn set_status_text(&mut self, text: &str) {
- self.state
- .call_log
- .push(format!("set_status_text: {}", text));
- self.state.status_text = text.to_owned();
- }
-
- fn clear_status_text(&mut self) {
- self.state.call_log.push(format!("clear_status_text"));
- self.state.status_text = "".to_owned();
- }
-
- fn set_download_text(&mut self, text: &str) {
- self.state
- .call_log
- .push(format!("set_download_text: {}", text));
- self.state.download_text = text.to_owned();
- }
-
- fn clear_download_text(&mut self) {
- self.state.call_log.push(format!("clear_download_text"));
- self.state.download_text = "".to_owned();
- }
-
- fn show_download_progress(&mut self) {
- self.state.call_log.push("show_download_progress".into());
- self.state.download_progress_visible = true;
- }
-
- fn hide_download_progress(&mut self) {
- self.state.call_log.push("hide_download_progress".into());
- self.state.download_progress_visible = false;
- }
-
- fn set_download_progress(&mut self, complete: u32) {
- self.state
- .call_log
- .push(format!("set_download_progress: {}", complete));
- self.state.download_progress = complete;
- }
-
- fn clear_download_progress(&mut self) {
- self.state.call_log.push(format!("clear_download_progress"));
- self.state.download_progress = 0;
- }
-
- fn show_download_button(&mut self) {
- self.state.call_log.push("show_download_button".into());
- self.state.download_button_visible = true;
- }
-
- fn hide_download_button(&mut self) {
- self.state.call_log.push("hide_download_button".into());
- self.state.download_button_visible = false;
- }
-
- fn enable_download_button(&mut self) {
- self.state.call_log.push("enable_download_button".into());
- self.state.download_button_enabled = true;
- }
-
- fn disable_download_button(&mut self) {
- self.state.call_log.push("disable_download_button".into());
- self.state.download_button_enabled = false;
- }
-
- fn show_cancel_button(&mut self) {
- self.state.call_log.push("show_cancel_button".into());
- self.state.cancel_button_visible = true;
- }
-
- fn hide_cancel_button(&mut self) {
- self.state.call_log.push("hide_cancel_button".into());
- self.state.cancel_button_visible = false;
- }
-
- fn enable_cancel_button(&mut self) {
- self.state.call_log.push("enable_cancel_button".into());
- self.state.cancel_button_enabled = true;
- }
-
- fn disable_cancel_button(&mut self) {
- self.state.call_log.push("disable_cancel_button".into());
- self.state.cancel_button_enabled = false;
- }
-
- fn show_beta_text(&mut self) {
- self.state.call_log.push("show_beta_text".into());
- self.state.beta_text_visible = true;
- }
-
- fn hide_beta_text(&mut self) {
- self.state.call_log.push("hide_beta_text".into());
- self.state.beta_text_visible = false;
- }
-
- fn show_stable_text(&mut self) {
- self.state.call_log.push("show_stable_text".into());
- self.state.stable_text_visible = true;
- }
-
- fn hide_stable_text(&mut self) {
- self.state.call_log.push("hide_stable_text".into());
- self.state.stable_text_visible = false;
- }
-
- fn show_error_message(&mut self, message: ErrorMessage) {
- self.state.call_log.push(format!(
- "show_error_message: {}. retry: {}. cancel: {}",
- message.status_text, message.retry_button_text, message.cancel_button_text
- ));
- self.state.error_message = message;
- self.state.error_message_visible = true;
- }
-
- fn hide_error_message(&mut self) {
- self.state.call_log.push("hide_error_message".into());
- self.state.error_message_visible = false;
- }
-
- fn on_error_message_cancel<F>(&mut self, callback: F)
- where
- F: Fn() + Send + 'static,
- {
- self.state.call_log.push("on_error_message_cancel".into());
- self.error_cancel_callback = Some(Box::new(callback));
- }
-
- fn on_error_message_retry<F>(&mut self, callback: F)
- where
- F: Fn() + Send + 'static,
- {
- self.state.call_log.push("on_error_message_retry".into());
- self.error_retry_callback = Some(Box::new(callback));
- }
-
- fn quit(&mut self) {
- self.state.call_log.push("quit".into());
- self.state.quit = true;
- }
-
- fn queue(&self) -> Self::Queue {
- self.queue.clone()
- }
-}
+mod mock;
/// Test that the flow starts by fetching app version data
#[tokio::test(start_paused = true)]
diff --git a/installer-downloader/tests/mock.rs b/installer-downloader/tests/mock.rs
new file mode 100644
index 0000000000..5e4a08d44b
--- /dev/null
+++ b/installer-downloader/tests/mock.rs
@@ -0,0 +1,365 @@
+#![cfg(any(target_os = "windows", target_os = "macos"))]
+
+//! This module contains fake/mock implementations of different updater/installer traits
+
+use installer_downloader::delegate::{AppDelegate, AppDelegateQueue, ErrorMessage};
+use installer_downloader::environment::{Architecture, Environment};
+use installer_downloader::temp::DirectoryProvider;
+use installer_downloader::ui_downloader::UiAppDownloaderParameters;
+use mullvad_update::api::VersionInfoProvider;
+use mullvad_update::app::{AppDownloader, DownloadError};
+use mullvad_update::fetch::ProgressUpdater;
+use mullvad_update::version::{Version, VersionInfo, VersionParameters};
+use std::io;
+use std::path::{Path, PathBuf};
+use std::sync::{Arc, LazyLock, Mutex};
+use std::vec::Vec;
+
+pub struct FakeVersionInfoProvider {}
+
+pub static FAKE_VERSION: LazyLock<VersionInfo> = LazyLock::new(|| VersionInfo {
+ stable: Version {
+ version: "2025.1".parse().unwrap(),
+ urls: vec!["https://mullvad.net/fakeapp".to_owned()],
+ size: 1234,
+ changelog: "a changelog".to_owned(),
+ sha256: [0u8; 32],
+ },
+ beta: None,
+});
+
+pub const FAKE_ENVIRONMENT: Environment = Environment {
+ architecture: Architecture::X86,
+};
+
+#[async_trait::async_trait]
+impl VersionInfoProvider for FakeVersionInfoProvider {
+ async fn get_version_info(&self, _params: VersionParameters) -> anyhow::Result<VersionInfo> {
+ Ok(FAKE_VERSION.clone())
+ }
+}
+
+pub struct FakeDirectoryProvider<const SUCCEED: bool> {}
+
+#[async_trait::async_trait]
+impl<const SUCCEEDED: bool> DirectoryProvider for FakeDirectoryProvider<SUCCEEDED> {
+ async fn create_download_dir() -> anyhow::Result<PathBuf> {
+ if SUCCEEDED {
+ Ok(Path::new("/tmp/fake").to_owned())
+ } else {
+ anyhow::bail!("Failed to create directory");
+ }
+ }
+}
+
+/// Downloader for which all steps immediately succeed
+pub type FakeAppDownloaderHappyPath = FakeAppDownloader<true, true, true>;
+
+/// Downloader for which the verification step fails
+pub type FakeAppDownloaderVerifyFail = FakeAppDownloader<true, false, false>;
+
+impl<const A: bool, const B: bool, const C: bool> From<UiAppDownloaderParameters<FakeAppDelegate>>
+ for FakeAppDownloader<A, B, C>
+{
+ fn from(params: UiAppDownloaderParameters<FakeAppDelegate>) -> Self {
+ FakeAppDownloader { params }
+ }
+}
+
+/// Fake app downloader
+///
+/// Parameters:
+/// * EXE_SUCCEED - whether fetching the binary succeeds
+/// * VERIFY_SUCCEED - whether verifying the binary succeeds
+/// * LAUNCH_SUCCEED - whether launching the binary succeeds
+pub struct FakeAppDownloader<
+ const EXE_SUCCEED: bool,
+ const VERIFY_SUCCEED: bool,
+ const LAUNCH_SUCCEED: bool,
+> {
+ params: UiAppDownloaderParameters<FakeAppDelegate>,
+}
+
+#[async_trait::async_trait]
+impl<const EXE_SUCCEED: bool, const VERIFY_SUCCEED: bool, const LAUNCH_SUCCEED: bool> AppDownloader
+ for FakeAppDownloader<EXE_SUCCEED, VERIFY_SUCCEED, LAUNCH_SUCCEED>
+{
+ async fn download_executable(&mut self) -> Result<(), DownloadError> {
+ self.params.app_progress.set_url(&self.params.app_url);
+ self.params.app_progress.clear_progress();
+ if EXE_SUCCEED {
+ self.params.app_progress.set_progress(1.);
+ Ok(())
+ } else {
+ Err(DownloadError::FetchApp(anyhow::anyhow!(
+ "fetching app failed"
+ )))
+ }
+ }
+
+ async fn verify(&mut self) -> Result<(), DownloadError> {
+ if VERIFY_SUCCEED {
+ Ok(())
+ } else {
+ Err(DownloadError::Verification(anyhow::anyhow!(
+ "verification failed"
+ )))
+ }
+ }
+
+ async fn install(&mut self) -> Result<(), DownloadError> {
+ if LAUNCH_SUCCEED {
+ Ok(())
+ } else {
+ Err(DownloadError::InstallFailed(io::Error::other(
+ "install failed",
+ )))
+ }
+ }
+}
+
+/// A fake queue that stores callbacks so that tests can run them later.
+#[derive(Clone, Default)]
+pub struct FakeQueue {
+ callbacks: Arc<Mutex<Vec<MainThreadCallback>>>,
+}
+
+pub type MainThreadCallback = Box<dyn FnOnce(&mut FakeAppDelegate) + Send>;
+
+impl FakeQueue {
+ /// Run all queued callbacks on the given delegate.
+ pub fn run_callbacks(&self, delegate: &mut FakeAppDelegate) {
+ let mut callbacks = self.callbacks.lock().unwrap();
+ for cb in callbacks.drain(..) {
+ cb(delegate);
+ }
+ }
+}
+
+impl AppDelegateQueue<FakeAppDelegate> for FakeQueue {
+ fn queue_main<F: FnOnce(&mut FakeAppDelegate) + 'static + Send>(&self, callback: F) {
+ self.callbacks.lock().unwrap().push(Box::new(callback));
+ }
+}
+
+/// A fake [AppDelegate]
+#[derive(Default)]
+pub struct FakeAppDelegate {
+ /// Callback registered by `on_download`
+ pub download_callback: Option<Box<dyn Fn() + Send>>,
+ /// Callback registered by `on_cancel`
+ pub cancel_callback: Option<Box<dyn Fn() + Send>>,
+ /// Callback registered by `on_beta_link`
+ pub beta_callback: Option<Box<dyn Fn() + Send>>,
+ /// Callback registered by `on_stable_link`
+ pub stable_callback: Option<Box<dyn Fn() + Send>>,
+ /// Callback registered by `on_error_cancel`
+ pub error_cancel_callback: Option<Box<dyn Fn() + Send>>,
+ /// Callback registered by `on_error_retry`
+ pub error_retry_callback: Option<Box<dyn Fn() + Send>>,
+ /// State of delegate
+ pub state: DelegateState,
+ /// Queue used to simulate the main thread
+ pub queue: FakeQueue,
+}
+
+/// A complete state of the UI, including its call history
+#[derive(Default, serde::Serialize)]
+pub struct DelegateState {
+ pub status_text: String,
+ pub download_text: String,
+ pub download_button_visible: bool,
+ pub cancel_button_visible: bool,
+ pub cancel_button_enabled: bool,
+ pub download_button_enabled: bool,
+ pub download_progress: u32,
+ pub download_progress_visible: bool,
+ pub beta_text_visible: bool,
+ pub stable_text_visible: bool,
+ pub error_message_visible: bool,
+ pub error_message: ErrorMessage,
+ pub quit: bool,
+ /// Record of method calls.
+ pub call_log: Vec<String>,
+}
+
+impl AppDelegate for FakeAppDelegate {
+ type Queue = FakeQueue;
+
+ fn on_download<F>(&mut self, callback: F)
+ where
+ F: Fn() + Send + 'static,
+ {
+ self.state.call_log.push("on_download".into());
+ self.download_callback = Some(Box::new(callback));
+ }
+
+ fn on_cancel<F>(&mut self, callback: F)
+ where
+ F: Fn() + Send + 'static,
+ {
+ self.state.call_log.push("on_cancel".into());
+ self.cancel_callback = Some(Box::new(callback));
+ }
+
+ fn on_beta_link<F>(&mut self, callback: F)
+ where
+ F: Fn() + Send + 'static,
+ {
+ self.state.call_log.push("on_beta_link".into());
+ self.beta_callback = Some(Box::new(callback));
+ }
+
+ fn on_stable_link<F>(&mut self, callback: F)
+ where
+ F: Fn() + Send + 'static,
+ {
+ self.state.call_log.push("on_stable_link".into());
+ self.stable_callback = Some(Box::new(callback));
+ }
+
+ fn set_status_text(&mut self, text: &str) {
+ self.state
+ .call_log
+ .push(format!("set_status_text: {}", text));
+ self.state.status_text = text.to_owned();
+ }
+
+ fn clear_status_text(&mut self) {
+ self.state.call_log.push("clear_status_text".into());
+ self.state.status_text = "".to_owned();
+ }
+
+ fn set_download_text(&mut self, text: &str) {
+ self.state
+ .call_log
+ .push(format!("set_download_text: {}", text));
+ self.state.download_text = text.to_owned();
+ }
+
+ fn clear_download_text(&mut self) {
+ self.state.call_log.push("clear_download_text".into());
+ self.state.download_text = "".to_owned();
+ }
+
+ fn show_download_progress(&mut self) {
+ self.state.call_log.push("show_download_progress".into());
+ self.state.download_progress_visible = true;
+ }
+
+ fn hide_download_progress(&mut self) {
+ self.state.call_log.push("hide_download_progress".into());
+ self.state.download_progress_visible = false;
+ }
+
+ fn set_download_progress(&mut self, complete: u32) {
+ self.state
+ .call_log
+ .push(format!("set_download_progress: {}", complete));
+ self.state.download_progress = complete;
+ }
+
+ fn clear_download_progress(&mut self) {
+ self.state.call_log.push("clear_download_progress".into());
+ self.state.download_progress = 0;
+ }
+
+ fn show_download_button(&mut self) {
+ self.state.call_log.push("show_download_button".into());
+ self.state.download_button_visible = true;
+ }
+
+ fn hide_download_button(&mut self) {
+ self.state.call_log.push("hide_download_button".into());
+ self.state.download_button_visible = false;
+ }
+
+ fn enable_download_button(&mut self) {
+ self.state.call_log.push("enable_download_button".into());
+ self.state.download_button_enabled = true;
+ }
+
+ fn disable_download_button(&mut self) {
+ self.state.call_log.push("disable_download_button".into());
+ self.state.download_button_enabled = false;
+ }
+
+ fn show_cancel_button(&mut self) {
+ self.state.call_log.push("show_cancel_button".into());
+ self.state.cancel_button_visible = true;
+ }
+
+ fn hide_cancel_button(&mut self) {
+ self.state.call_log.push("hide_cancel_button".into());
+ self.state.cancel_button_visible = false;
+ }
+
+ fn enable_cancel_button(&mut self) {
+ self.state.call_log.push("enable_cancel_button".into());
+ self.state.cancel_button_enabled = true;
+ }
+
+ fn disable_cancel_button(&mut self) {
+ self.state.call_log.push("disable_cancel_button".into());
+ self.state.cancel_button_enabled = false;
+ }
+
+ fn show_beta_text(&mut self) {
+ self.state.call_log.push("show_beta_text".into());
+ self.state.beta_text_visible = true;
+ }
+
+ fn hide_beta_text(&mut self) {
+ self.state.call_log.push("hide_beta_text".into());
+ self.state.beta_text_visible = false;
+ }
+
+ fn show_stable_text(&mut self) {
+ self.state.call_log.push("show_stable_text".into());
+ self.state.stable_text_visible = true;
+ }
+
+ fn hide_stable_text(&mut self) {
+ self.state.call_log.push("hide_stable_text".into());
+ self.state.stable_text_visible = false;
+ }
+
+ fn show_error_message(&mut self, message: ErrorMessage) {
+ self.state.call_log.push(format!(
+ "show_error_message: {}. retry: {}. cancel: {}",
+ message.status_text, message.retry_button_text, message.cancel_button_text
+ ));
+ self.state.error_message = message;
+ self.state.error_message_visible = true;
+ }
+
+ fn hide_error_message(&mut self) {
+ self.state.call_log.push("hide_error_message".into());
+ self.state.error_message_visible = false;
+ }
+
+ fn on_error_message_cancel<F>(&mut self, callback: F)
+ where
+ F: Fn() + Send + 'static,
+ {
+ self.state.call_log.push("on_error_message_cancel".into());
+ self.error_cancel_callback = Some(Box::new(callback));
+ }
+
+ fn on_error_message_retry<F>(&mut self, callback: F)
+ where
+ F: Fn() + Send + 'static,
+ {
+ self.state.call_log.push("on_error_message_retry".into());
+ self.error_retry_callback = Some(Box::new(callback));
+ }
+
+ fn quit(&mut self) {
+ self.state.call_log.push("quit".into());
+ self.state.quit = true;
+ }
+
+ fn queue(&self) -> Self::Queue {
+ self.queue.clone()
+ }
+}