pake-cli 3.11.4 → 3.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli.js +335 -161
- package/dist/pake-badge-test.html +1 -0
- package/dist/test-local.html +1 -0
- package/package.json +1 -1
- package/src-tauri/Cargo.lock +7 -4
- package/src-tauri/Cargo.toml +6 -1
- package/src-tauri/build.rs +2 -0
- package/src-tauri/gen/schemas/acl-manifests.json +1 -0
- package/src-tauri/gen/schemas/capabilities.json +1 -0
- package/src-tauri/gen/schemas/desktop-schema.json +3331 -0
- package/src-tauri/gen/schemas/macOS-schema.json +3331 -0
- package/src-tauri/pake.json +1 -0
- package/src-tauri/src/app/config.rs +2 -0
- package/src-tauri/src/app/invoke.rs +85 -0
- package/src-tauri/src/app/menu.rs +56 -5
- package/src-tauri/src/app/setup.rs +39 -34
- package/src-tauri/src/app/window.rs +64 -17
- package/src-tauri/src/inject/event.js +98 -12
- package/src-tauri/src/inject/find.js +708 -0
- package/src-tauri/src/inject/{component.js → fullscreen.js} +7 -41
- package/src-tauri/src/inject/toast.js +22 -0
- package/src-tauri/src/lib.rs +13 -5
- package/src-tauri/src/util.rs +131 -24
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -1,28 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
document.body.appendChild(m);
|
|
9
|
-
setTimeout(function () {
|
|
10
|
-
const d = 0.5;
|
|
11
|
-
m.style.transition =
|
|
12
|
-
"transform " + d + "s ease-in, opacity " + d + "s ease-in";
|
|
13
|
-
m.style.opacity = "0";
|
|
14
|
-
setTimeout(function () {
|
|
15
|
-
document.body.removeChild(m);
|
|
16
|
-
}, d * 1000);
|
|
17
|
-
}, 3000);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
window.pakeToast = pakeToast;
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// Polyfill for HTML5 Fullscreen API in Tauri webview
|
|
24
|
-
// This bridges the HTML5 Fullscreen API to Tauri's native window fullscreen
|
|
25
|
-
// Works for all video sites (YouTube, Vimeo, Bilibili, etc.)
|
|
1
|
+
// Polyfill for HTML5 Fullscreen API in Tauri webview.
|
|
2
|
+
// Bridges the standard requestFullscreen / exitFullscreen DOM API to Tauri's
|
|
3
|
+
// native window fullscreen so video sites (YouTube, Vimeo, Bilibili, etc.) can
|
|
4
|
+
// go true fullscreen on their player buttons.
|
|
5
|
+
//
|
|
6
|
+
// Split out from component.js so a future CLI flag (or custom.js override)
|
|
7
|
+
// can short-circuit the polyfill for apps that don't need video fullscreen.
|
|
26
8
|
(function () {
|
|
27
9
|
if (window.__PAKE_FULLSCREEN_POLYFILL__) return;
|
|
28
10
|
window.__PAKE_FULLSCREEN_POLYFILL__ = true;
|
|
@@ -42,7 +24,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
42
24
|
let wasInBody = false;
|
|
43
25
|
let monitorId = null;
|
|
44
26
|
|
|
45
|
-
// Inject fullscreen styles
|
|
46
27
|
if (!document.getElementById("pake-fullscreen-style")) {
|
|
47
28
|
const styleEl = document.createElement("style");
|
|
48
29
|
styleEl.id = "pake-fullscreen-style";
|
|
@@ -93,7 +74,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
93
74
|
monitorId = null;
|
|
94
75
|
}
|
|
95
76
|
|
|
96
|
-
// Find the actual video element
|
|
97
77
|
function findMediaElement() {
|
|
98
78
|
const videos = document.querySelectorAll("video");
|
|
99
79
|
if (videos.length > 0) {
|
|
@@ -112,11 +92,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
112
92
|
return null;
|
|
113
93
|
}
|
|
114
94
|
|
|
115
|
-
// Enter fullscreen
|
|
116
95
|
function enterFullscreen(element) {
|
|
117
96
|
fullscreenElement = element;
|
|
118
97
|
|
|
119
|
-
// If html/body element, find the video instead
|
|
120
98
|
let targetElement = element;
|
|
121
99
|
if (element === document.documentElement || element === document.body) {
|
|
122
100
|
const mediaElement = findMediaElement();
|
|
@@ -130,7 +108,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
130
108
|
actualFullscreenElement = element;
|
|
131
109
|
}
|
|
132
110
|
|
|
133
|
-
// Save original state
|
|
134
111
|
originalStyles = {
|
|
135
112
|
position: targetElement.style.position,
|
|
136
113
|
top: targetElement.style.top,
|
|
@@ -152,7 +129,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
152
129
|
originalNextSibling = targetElement.nextSibling;
|
|
153
130
|
}
|
|
154
131
|
|
|
155
|
-
// Apply fullscreen
|
|
156
132
|
targetElement.classList.add("pake-fullscreen-element");
|
|
157
133
|
document.body.classList.add("pake-fullscreen-active");
|
|
158
134
|
|
|
@@ -160,7 +136,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
160
136
|
document.body.appendChild(targetElement);
|
|
161
137
|
}
|
|
162
138
|
|
|
163
|
-
// Fullscreen window
|
|
164
139
|
appWindow.setFullscreen(true).then(() => {
|
|
165
140
|
startFullscreenMonitor();
|
|
166
141
|
const event = new Event("fullscreenchange", { bubbles: true });
|
|
@@ -177,7 +152,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
177
152
|
return Promise.resolve();
|
|
178
153
|
}
|
|
179
154
|
|
|
180
|
-
// Exit fullscreen
|
|
181
155
|
function exitFullscreen() {
|
|
182
156
|
if (!fullscreenElement) {
|
|
183
157
|
return Promise.resolve();
|
|
@@ -188,7 +162,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
188
162
|
const exitingElement = fullscreenElement;
|
|
189
163
|
const targetElement = actualFullscreenElement;
|
|
190
164
|
|
|
191
|
-
// Restore styles and position
|
|
192
165
|
targetElement.classList.remove("pake-fullscreen-element");
|
|
193
166
|
document.body.classList.remove("pake-fullscreen-active");
|
|
194
167
|
|
|
@@ -209,7 +182,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
209
182
|
}
|
|
210
183
|
}
|
|
211
184
|
|
|
212
|
-
// Reset state
|
|
213
185
|
fullscreenElement = null;
|
|
214
186
|
actualFullscreenElement = null;
|
|
215
187
|
originalStyles = null;
|
|
@@ -217,7 +189,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
217
189
|
originalNextSibling = null;
|
|
218
190
|
wasInBody = false;
|
|
219
191
|
|
|
220
|
-
// Exit window fullscreen
|
|
221
192
|
return appWindow.setFullscreen(false).then(() => {
|
|
222
193
|
const event = new Event("fullscreenchange", { bubbles: true });
|
|
223
194
|
document.dispatchEvent(event);
|
|
@@ -231,7 +202,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
231
202
|
});
|
|
232
203
|
}
|
|
233
204
|
|
|
234
|
-
// Override fullscreenEnabled
|
|
235
205
|
Object.defineProperty(document, "fullscreenEnabled", {
|
|
236
206
|
get: () => true,
|
|
237
207
|
configurable: true,
|
|
@@ -241,7 +211,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
241
211
|
configurable: true,
|
|
242
212
|
});
|
|
243
213
|
|
|
244
|
-
// Override fullscreenElement
|
|
245
214
|
Object.defineProperty(document, "fullscreenElement", {
|
|
246
215
|
get: () => fullscreenElement,
|
|
247
216
|
configurable: true,
|
|
@@ -255,7 +224,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
255
224
|
configurable: true,
|
|
256
225
|
});
|
|
257
226
|
|
|
258
|
-
// Override requestFullscreen
|
|
259
227
|
Element.prototype.requestFullscreen = function () {
|
|
260
228
|
return enterFullscreen(this);
|
|
261
229
|
};
|
|
@@ -266,12 +234,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
266
234
|
return enterFullscreen(this);
|
|
267
235
|
};
|
|
268
236
|
|
|
269
|
-
// Override exitFullscreen
|
|
270
237
|
document.exitFullscreen = exitFullscreen;
|
|
271
238
|
document.webkitExitFullscreen = exitFullscreen;
|
|
272
239
|
document.webkitCancelFullScreen = exitFullscreen;
|
|
273
240
|
|
|
274
|
-
// Handle Escape key
|
|
275
241
|
document.addEventListener(
|
|
276
242
|
"keydown",
|
|
277
243
|
(e) => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Lightweight in-page toast used by Rust `show_toast` (download status, etc).
|
|
2
|
+
// Kept tiny and always loaded so the Rust side can rely on `window.pakeToast`.
|
|
3
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
4
|
+
function pakeToast(msg) {
|
|
5
|
+
const m = document.createElement("div");
|
|
6
|
+
m.innerHTML = msg;
|
|
7
|
+
m.style.cssText =
|
|
8
|
+
"max-width:60%;min-width: 80px;padding:0 12px;height: 32px;color: rgb(255, 255, 255);line-height: 32px;text-align: center;border-radius: 8px;position: fixed; bottom:24px;right: 28px;z-index: 999999;background: rgba(0, 0, 0,.8);font-size: 13px;";
|
|
9
|
+
document.body.appendChild(m);
|
|
10
|
+
setTimeout(function () {
|
|
11
|
+
const d = 0.5;
|
|
12
|
+
m.style.transition =
|
|
13
|
+
"transform " + d + "s ease-in, opacity " + d + "s ease-in";
|
|
14
|
+
m.style.opacity = "0";
|
|
15
|
+
setTimeout(function () {
|
|
16
|
+
document.body.removeChild(m);
|
|
17
|
+
}, d * 1000);
|
|
18
|
+
}, 3000);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
window.pakeToast = pakeToast;
|
|
22
|
+
});
|
package/src-tauri/src/lib.rs
CHANGED
|
@@ -13,7 +13,8 @@ const WINDOW_SHOW_DELAY: u64 = 50;
|
|
|
13
13
|
|
|
14
14
|
use app::{
|
|
15
15
|
invoke::{
|
|
16
|
-
clear_cache_and_restart, download_file, download_file_by_binary,
|
|
16
|
+
clear_cache_and_restart, clear_dock_badge, download_file, download_file_by_binary,
|
|
17
|
+
increment_dock_badge, send_notification, set_dock_badge, set_dock_badge_label,
|
|
17
18
|
update_theme_mode,
|
|
18
19
|
},
|
|
19
20
|
setup::{set_global_shortcut, set_system_tray},
|
|
@@ -42,6 +43,7 @@ pub fn run_app() {
|
|
|
42
43
|
let start_to_tray = pake_config.windows[0].start_to_tray && show_system_tray; // Only valid when tray is enabled
|
|
43
44
|
let multi_instance = pake_config.multi_instance;
|
|
44
45
|
let multi_window = pake_config.multi_window;
|
|
46
|
+
let enable_find = pake_config.windows[0].enable_find;
|
|
45
47
|
|
|
46
48
|
let window_state_plugin = WindowStatePlugin::default()
|
|
47
49
|
.with_state_flags(if init_fullscreen {
|
|
@@ -81,6 +83,10 @@ pub fn run_app() {
|
|
|
81
83
|
download_file,
|
|
82
84
|
download_file_by_binary,
|
|
83
85
|
send_notification,
|
|
86
|
+
increment_dock_badge,
|
|
87
|
+
set_dock_badge,
|
|
88
|
+
set_dock_badge_label,
|
|
89
|
+
clear_dock_badge,
|
|
84
90
|
update_theme_mode,
|
|
85
91
|
clear_cache_and_restart,
|
|
86
92
|
])
|
|
@@ -93,8 +99,7 @@ pub fn run_app() {
|
|
|
93
99
|
// --- Menu Construction Start ---
|
|
94
100
|
#[cfg(target_os = "macos")]
|
|
95
101
|
{
|
|
96
|
-
|
|
97
|
-
app.set_menu(menu)?;
|
|
102
|
+
app::menu::set_app_menu(app.app_handle(), multi_window, enable_find)?;
|
|
98
103
|
|
|
99
104
|
// Event Handling for Custom Menu Item
|
|
100
105
|
app.on_menu_event(move |app_handle, event| {
|
|
@@ -103,7 +108,7 @@ pub fn run_app() {
|
|
|
103
108
|
}
|
|
104
109
|
// --- Menu Construction End ---
|
|
105
110
|
|
|
106
|
-
let window = set_window(app.app_handle(), &pake_config, &tauri_config)
|
|
111
|
+
let window = set_window(app.app_handle(), &pake_config, &tauri_config)?;
|
|
107
112
|
set_system_tray(
|
|
108
113
|
app.app_handle(),
|
|
109
114
|
show_system_tray,
|
|
@@ -174,7 +179,10 @@ pub fn run_app() {
|
|
|
174
179
|
}
|
|
175
180
|
})
|
|
176
181
|
.build(tauri::generate_context!())
|
|
177
|
-
.
|
|
182
|
+
.unwrap_or_else(|error| {
|
|
183
|
+
eprintln!("[Pake] Fatal error while building Tauri application: {error}");
|
|
184
|
+
std::process::exit(1);
|
|
185
|
+
})
|
|
178
186
|
.run(|_app, _event| {
|
|
179
187
|
// Handle macOS dock icon click to reopen hidden window
|
|
180
188
|
#[cfg(target_os = "macos")]
|
package/src-tauri/src/util.rs
CHANGED
|
@@ -23,25 +23,35 @@ pub fn get_pake_config() -> (PakeConfig, Config) {
|
|
|
23
23
|
(pake_config, tauri_config)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
pub fn get_data_dir(app: &AppHandle, package_name: String) -> PathBuf {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
pub fn get_data_dir(app: &AppHandle, package_name: String) -> std::io::Result<PathBuf> {
|
|
27
|
+
let data_dir = app
|
|
28
|
+
.path()
|
|
29
|
+
.config_dir()
|
|
30
|
+
.map_err(|err| {
|
|
31
|
+
std::io::Error::new(
|
|
32
|
+
std::io::ErrorKind::NotFound,
|
|
33
|
+
format!("Failed to resolve config dir: {err}"),
|
|
34
|
+
)
|
|
35
|
+
})?
|
|
36
|
+
.join(package_name);
|
|
37
|
+
|
|
38
|
+
if !data_dir.exists() {
|
|
39
|
+
std::fs::create_dir_all(&data_dir).map_err(|err| {
|
|
40
|
+
std::io::Error::new(
|
|
41
|
+
err.kind(),
|
|
42
|
+
format!("Can't create dir {}: {err}", data_dir.display()),
|
|
43
|
+
)
|
|
44
|
+
})?;
|
|
39
45
|
}
|
|
46
|
+
|
|
47
|
+
Ok(data_dir)
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
pub fn show_toast(window: &WebviewWindow, message: &str) {
|
|
43
51
|
let script = format!(r#"pakeToast("{message}");"#);
|
|
44
|
-
window.eval(&script)
|
|
52
|
+
if let Err(error) = window.eval(&script) {
|
|
53
|
+
eprintln!("[Pake] Failed to show toast: {error}");
|
|
54
|
+
}
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
pub enum MessageType {
|
|
@@ -101,10 +111,16 @@ pub fn get_download_message_with_lang(
|
|
|
101
111
|
.to_string()
|
|
102
112
|
}
|
|
103
113
|
|
|
104
|
-
|
|
114
|
+
/// Check if the file exists. If it does, append `-N` to the stem until a free
|
|
115
|
+
/// path is found.
|
|
116
|
+
///
|
|
117
|
+
/// Robustness notes:
|
|
118
|
+
/// - Files without an extension are handled (we keep them extensionless).
|
|
119
|
+
/// - If the numeric suffix would overflow `u32::MAX` we fall back to the
|
|
120
|
+
/// original file_path so the caller never enters an infinite loop on
|
|
121
|
+
/// pathologically large filenames (regression guard for #1183).
|
|
105
122
|
pub fn check_file_or_append(file_path: &str) -> String {
|
|
106
123
|
let mut new_path = PathBuf::from(file_path);
|
|
107
|
-
let mut counter = 0;
|
|
108
124
|
|
|
109
125
|
while new_path.exists() {
|
|
110
126
|
let file_stem = new_path
|
|
@@ -116,16 +132,24 @@ pub fn check_file_or_append(file_path: &str) -> String {
|
|
|
116
132
|
.map(|e| e.to_string_lossy().to_string());
|
|
117
133
|
let parent_dir = new_path.parent().unwrap_or(Path::new(""));
|
|
118
134
|
|
|
119
|
-
let
|
|
120
|
-
|
|
135
|
+
let parsed_suffix = file_stem.rfind('-').and_then(|index| {
|
|
136
|
+
file_stem[index + 1..]
|
|
137
|
+
.parse::<u32>()
|
|
138
|
+
.ok()
|
|
139
|
+
.map(|n| (index, n))
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
let new_file_stem = match parsed_suffix {
|
|
143
|
+
Some((index, current)) => {
|
|
144
|
+
let Some(next) = current.checked_add(1) else {
|
|
145
|
+
// u32::MAX collisions are a sign of something pathological;
|
|
146
|
+
// bail with the original path instead of looping forever.
|
|
147
|
+
return file_path.to_string();
|
|
148
|
+
};
|
|
121
149
|
let base_name = &file_stem[..index];
|
|
122
|
-
|
|
123
|
-
format!("{base_name}-{counter}")
|
|
124
|
-
}
|
|
125
|
-
_ => {
|
|
126
|
-
counter += 1;
|
|
127
|
-
format!("{file_stem}-{counter}")
|
|
150
|
+
format!("{base_name}-{next}")
|
|
128
151
|
}
|
|
152
|
+
None => format!("{file_stem}-1"),
|
|
129
153
|
};
|
|
130
154
|
|
|
131
155
|
new_path = match &extension {
|
|
@@ -136,3 +160,86 @@ pub fn check_file_or_append(file_path: &str) -> String {
|
|
|
136
160
|
|
|
137
161
|
new_path.to_string_lossy().into_owned()
|
|
138
162
|
}
|
|
163
|
+
|
|
164
|
+
#[cfg(test)]
|
|
165
|
+
mod tests {
|
|
166
|
+
use super::*;
|
|
167
|
+
use std::env;
|
|
168
|
+
use std::fs;
|
|
169
|
+
use std::path::PathBuf;
|
|
170
|
+
|
|
171
|
+
fn temp_path(name: &str) -> PathBuf {
|
|
172
|
+
let mut dir = env::temp_dir();
|
|
173
|
+
dir.push(format!(
|
|
174
|
+
"pake-util-test-{}-{}",
|
|
175
|
+
std::process::id(),
|
|
176
|
+
std::time::SystemTime::now()
|
|
177
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
178
|
+
.map(|d| d.as_nanos())
|
|
179
|
+
.unwrap_or(0)
|
|
180
|
+
));
|
|
181
|
+
fs::create_dir_all(&dir).unwrap();
|
|
182
|
+
dir.push(name);
|
|
183
|
+
dir
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[test]
|
|
187
|
+
fn check_file_or_append_returns_input_when_missing() {
|
|
188
|
+
let path = temp_path("ghost.txt");
|
|
189
|
+
let resolved = check_file_or_append(path.to_str().unwrap());
|
|
190
|
+
assert_eq!(resolved, path.to_string_lossy());
|
|
191
|
+
let _ = fs::remove_dir_all(path.parent().unwrap());
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#[test]
|
|
195
|
+
fn check_file_or_append_increments_suffix() {
|
|
196
|
+
let path = temp_path("dup.txt");
|
|
197
|
+
fs::write(&path, b"existing").unwrap();
|
|
198
|
+
let resolved = check_file_or_append(path.to_str().unwrap());
|
|
199
|
+
assert!(resolved.ends_with("dup-1.txt"), "got {resolved}");
|
|
200
|
+
let _ = fs::remove_dir_all(path.parent().unwrap());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#[test]
|
|
204
|
+
fn check_file_or_append_handles_files_without_extension() {
|
|
205
|
+
let path = temp_path("README");
|
|
206
|
+
fs::write(&path, b"existing").unwrap();
|
|
207
|
+
let resolved = check_file_or_append(path.to_str().unwrap());
|
|
208
|
+
assert!(resolved.ends_with("README-1"), "got {resolved}");
|
|
209
|
+
let _ = fs::remove_dir_all(path.parent().unwrap());
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[test]
|
|
213
|
+
fn check_file_or_append_does_not_panic_on_huge_suffix() {
|
|
214
|
+
let path = temp_path(&format!("huge-{}.txt", u32::MAX));
|
|
215
|
+
fs::write(&path, b"existing").unwrap();
|
|
216
|
+
let resolved = check_file_or_append(path.to_str().unwrap());
|
|
217
|
+
assert!(resolved.contains("huge-"));
|
|
218
|
+
let _ = fs::remove_dir_all(path.parent().unwrap());
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#[test]
|
|
222
|
+
fn download_message_falls_back_to_english_for_unknown_locale() {
|
|
223
|
+
let msg = get_download_message_with_lang(MessageType::Start, Some("fr-FR".to_string()));
|
|
224
|
+
assert_eq!(msg, "Start downloading~");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#[test]
|
|
228
|
+
fn download_message_picks_chinese_for_zh_locales() {
|
|
229
|
+
for tag in ["zh", "zh-CN", "zh-TW", "en-CN", "en-HK"] {
|
|
230
|
+
let msg = get_download_message_with_lang(MessageType::Success, Some(tag.to_string()));
|
|
231
|
+
assert_eq!(
|
|
232
|
+
msg, "下载成功,已保存到下载目录~",
|
|
233
|
+
"expected Chinese for {tag}"
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#[test]
|
|
239
|
+
fn download_message_failure_localized() {
|
|
240
|
+
let en = get_download_message_with_lang(MessageType::Failure, Some("en".into()));
|
|
241
|
+
let zh = get_download_message_with_lang(MessageType::Failure, Some("zh".into()));
|
|
242
|
+
assert!(en.contains("Download failed"));
|
|
243
|
+
assert!(zh.contains("下载失败"));
|
|
244
|
+
}
|
|
245
|
+
}
|