1use js_sys::Object;
5
6mod buttons;
8mod core;
9mod dialogs;
10mod events;
11mod lifecycle;
12mod navigation;
13mod permissions;
14mod theme;
15pub mod types;
16mod viewport;
17
18pub use types::{
20 BackgroundEvent, BottomButton, BottomButtonParams, EventHandle, OpenLinkOptions,
21 SafeAreaInset, SecondaryButtonParams, SecondaryButtonPosition
22};
23
24#[derive(Clone)]
26pub struct TelegramWebApp {
27 pub(super) inner: Object
28}
29
30#[cfg(test)]
31mod tests {
32 use std::{
33 cell::{Cell, RefCell},
34 rc::Rc
35 };
36
37 use js_sys::{Function, Object, Reflect};
38 use wasm_bindgen::{JsCast, JsValue, prelude::Closure};
39 use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
40 use web_sys::window;
41
42 use super::*;
43 use crate::core::types::download_file_params::DownloadFileParams;
44
45 wasm_bindgen_test_configure!(run_in_browser);
46
47 #[allow(dead_code)]
48 fn setup_webapp() -> Object {
49 let win = window().unwrap();
50 let telegram = Object::new();
51 let webapp = Object::new();
52 let _ = Reflect::set(&win, &"Telegram".into(), &telegram);
53 let _ = Reflect::set(&telegram, &"WebApp".into(), &webapp);
54 webapp
55 }
56
57 #[wasm_bindgen_test]
58 #[allow(dead_code, clippy::unused_unit)]
59 fn hide_keyboard_calls_js() {
60 let webapp = setup_webapp();
61 let called = Rc::new(Cell::new(false));
62 let called_clone = Rc::clone(&called);
63
64 let hide_cb = Closure::<dyn FnMut()>::new(move || {
65 called_clone.set(true);
66 });
67 let _ = Reflect::set(
68 &webapp,
69 &"hideKeyboard".into(),
70 hide_cb.as_ref().unchecked_ref()
71 );
72 hide_cb.forget();
73
74 let app = TelegramWebApp::instance().unwrap();
75 app.hide_keyboard().unwrap();
76 assert!(called.get());
77 }
78
79 #[wasm_bindgen_test]
80 #[allow(dead_code, clippy::unused_unit)]
81 fn hide_main_button_calls_js() {
82 let webapp = setup_webapp();
83 let main_button = Object::new();
84 let called = Rc::new(Cell::new(false));
85 let called_clone = Rc::clone(&called);
86
87 let hide_cb = Closure::<dyn FnMut()>::new(move || {
88 called_clone.set(true);
89 });
90 let _ = Reflect::set(
91 &main_button,
92 &"hide".into(),
93 hide_cb.as_ref().unchecked_ref()
94 );
95 hide_cb.forget();
96
97 let _ = Reflect::set(&webapp, &"MainButton".into(), &main_button);
98
99 let app = TelegramWebApp::instance().unwrap();
100 app.hide_bottom_button(BottomButton::Main).unwrap();
101 assert!(called.get());
102 }
103
104 #[wasm_bindgen_test]
105 #[allow(dead_code, clippy::unused_unit)]
106 fn hide_secondary_button_calls_js() {
107 let webapp = setup_webapp();
108 let secondary_button = Object::new();
109 let called = Rc::new(Cell::new(false));
110 let called_clone = Rc::clone(&called);
111
112 let hide_cb = Closure::<dyn FnMut()>::new(move || {
113 called_clone.set(true);
114 });
115 let _ = Reflect::set(
116 &secondary_button,
117 &"hide".into(),
118 hide_cb.as_ref().unchecked_ref()
119 );
120 hide_cb.forget();
121
122 let _ = Reflect::set(&webapp, &"SecondaryButton".into(), &secondary_button);
123
124 let app = TelegramWebApp::instance().unwrap();
125 app.hide_bottom_button(BottomButton::Secondary).unwrap();
126 assert!(called.get());
127 }
128
129 #[wasm_bindgen_test]
130 #[allow(dead_code, clippy::unused_unit)]
131 fn set_bottom_button_color_calls_js() {
132 let webapp = setup_webapp();
133 let main_button = Object::new();
134 let received = Rc::new(RefCell::new(None));
135 let rc_clone = Rc::clone(&received);
136
137 let set_color_cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
138 *rc_clone.borrow_mut() = v.as_string();
139 });
140 let _ = Reflect::set(
141 &main_button,
142 &"setColor".into(),
143 set_color_cb.as_ref().unchecked_ref()
144 );
145 set_color_cb.forget();
146
147 let _ = Reflect::set(&webapp, &"MainButton".into(), &main_button);
148
149 let app = TelegramWebApp::instance().unwrap();
150 app.set_bottom_button_color(BottomButton::Main, "#00ff00")
151 .unwrap();
152 assert_eq!(received.borrow().as_deref(), Some("#00ff00"));
153 }
154
155 #[wasm_bindgen_test]
156 #[allow(dead_code, clippy::unused_unit)]
157 fn set_secondary_button_color_calls_js() {
158 let webapp = setup_webapp();
159 let secondary_button = Object::new();
160 let received = Rc::new(RefCell::new(None));
161 let rc_clone = Rc::clone(&received);
162
163 let set_color_cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
164 *rc_clone.borrow_mut() = v.as_string();
165 });
166 let _ = Reflect::set(
167 &secondary_button,
168 &"setColor".into(),
169 set_color_cb.as_ref().unchecked_ref()
170 );
171 set_color_cb.forget();
172
173 let _ = Reflect::set(&webapp, &"SecondaryButton".into(), &secondary_button);
174
175 let app = TelegramWebApp::instance().unwrap();
176 app.set_bottom_button_color(BottomButton::Secondary, "#00ff00")
177 .unwrap();
178 assert_eq!(received.borrow().as_deref(), Some("#00ff00"));
179 }
180
181 #[wasm_bindgen_test]
182 #[allow(dead_code, clippy::unused_unit)]
183 fn set_bottom_button_text_color_calls_js() {
184 let webapp = setup_webapp();
185 let main_button = Object::new();
186 let received = Rc::new(RefCell::new(None));
187 let rc_clone = Rc::clone(&received);
188
189 let set_color_cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
190 *rc_clone.borrow_mut() = v.as_string();
191 });
192 let _ = Reflect::set(
193 &main_button,
194 &"setTextColor".into(),
195 set_color_cb.as_ref().unchecked_ref()
196 );
197 set_color_cb.forget();
198
199 let _ = Reflect::set(&webapp, &"MainButton".into(), &main_button);
200
201 let app = TelegramWebApp::instance().unwrap();
202 app.set_bottom_button_text_color(BottomButton::Main, "#112233")
203 .unwrap();
204 assert_eq!(received.borrow().as_deref(), Some("#112233"));
205 }
206
207 #[wasm_bindgen_test]
208 #[allow(dead_code, clippy::unused_unit)]
209 fn set_secondary_button_text_color_calls_js() {
210 let webapp = setup_webapp();
211 let secondary_button = Object::new();
212 let received = Rc::new(RefCell::new(None));
213 let rc_clone = Rc::clone(&received);
214
215 let set_color_cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
216 *rc_clone.borrow_mut() = v.as_string();
217 });
218 let _ = Reflect::set(
219 &secondary_button,
220 &"setTextColor".into(),
221 set_color_cb.as_ref().unchecked_ref()
222 );
223 set_color_cb.forget();
224
225 let _ = Reflect::set(&webapp, &"SecondaryButton".into(), &secondary_button);
226
227 let app = TelegramWebApp::instance().unwrap();
228 app.set_bottom_button_text_color(BottomButton::Secondary, "#112233")
229 .unwrap();
230 assert_eq!(received.borrow().as_deref(), Some("#112233"));
231 }
232
233 #[wasm_bindgen_test]
234 #[allow(dead_code, clippy::unused_unit)]
235 fn enable_bottom_button_calls_js() {
236 let webapp = setup_webapp();
237 let button = Object::new();
238 let called = Rc::new(Cell::new(false));
239 let called_clone = Rc::clone(&called);
240
241 let enable_cb = Closure::<dyn FnMut()>::new(move || {
242 called_clone.set(true);
243 });
244 let _ = Reflect::set(
245 &button,
246 &"enable".into(),
247 enable_cb.as_ref().unchecked_ref()
248 );
249 enable_cb.forget();
250
251 let _ = Reflect::set(&webapp, &"MainButton".into(), &button);
252
253 let app = TelegramWebApp::instance().unwrap();
254 app.enable_bottom_button(BottomButton::Main).unwrap();
255 assert!(called.get());
256 }
257
258 #[wasm_bindgen_test]
259 #[allow(dead_code, clippy::unused_unit)]
260 fn show_bottom_button_progress_passes_flag() {
261 let webapp = setup_webapp();
262 let button = Object::new();
263 let received = Rc::new(RefCell::new(None));
264 let rc_clone = Rc::clone(&received);
265
266 let cb = Closure::<dyn FnMut(JsValue)>::new(move |arg: JsValue| {
267 *rc_clone.borrow_mut() = arg.as_bool();
268 });
269 let _ = Reflect::set(&button, &"showProgress".into(), cb.as_ref().unchecked_ref());
270 cb.forget();
271
272 let _ = Reflect::set(&webapp, &"MainButton".into(), &button);
273
274 let app = TelegramWebApp::instance().unwrap();
275 app.show_bottom_button_progress(BottomButton::Main, true)
276 .unwrap();
277 assert_eq!(*received.borrow(), Some(true));
278 }
279
280 #[wasm_bindgen_test]
281 #[allow(dead_code, clippy::unused_unit)]
282 fn set_bottom_button_params_serializes() {
283 let webapp = setup_webapp();
284 let button = Object::new();
285 let received = Rc::new(RefCell::new(Object::new()));
286 let rc_clone = Rc::clone(&received);
287
288 let cb = Closure::<dyn FnMut(JsValue)>::new(move |value: JsValue| {
289 let obj = value.dyn_into::<Object>().expect("object");
290 rc_clone.replace(obj);
291 });
292 let _ = Reflect::set(&button, &"setParams".into(), cb.as_ref().unchecked_ref());
293 cb.forget();
294
295 let _ = Reflect::set(&webapp, &"MainButton".into(), &button);
296
297 let app = TelegramWebApp::instance().unwrap();
298 let params = BottomButtonParams {
299 text: Some("Send"),
300 color: Some("#ffffff"),
301 text_color: Some("#000000"),
302 is_active: Some(true),
303 is_visible: Some(true),
304 has_shine_effect: Some(false)
305 };
306 app.set_bottom_button_params(BottomButton::Main, ¶ms)
307 .unwrap();
308
309 let stored = received.borrow();
310 assert_eq!(
311 Reflect::get(&stored, &"text".into()).unwrap().as_string(),
312 Some("Send".to_string())
313 );
314 assert_eq!(
315 Reflect::get(&stored, &"color".into()).unwrap().as_string(),
316 Some("#ffffff".to_string())
317 );
318 assert_eq!(
319 Reflect::get(&stored, &"text_color".into())
320 .unwrap()
321 .as_string(),
322 Some("#000000".to_string())
323 );
324 }
325
326 #[wasm_bindgen_test]
327 #[allow(dead_code, clippy::unused_unit)]
328 fn set_secondary_button_params_serializes_position() {
329 let webapp = setup_webapp();
330 let button = Object::new();
331 let received = Rc::new(RefCell::new(Object::new()));
332 let rc_clone = Rc::clone(&received);
333
334 let cb = Closure::<dyn FnMut(JsValue)>::new(move |value: JsValue| {
335 let obj = value.dyn_into::<Object>().expect("object");
336 rc_clone.replace(obj);
337 });
338 let _ = Reflect::set(&button, &"setParams".into(), cb.as_ref().unchecked_ref());
339 cb.forget();
340
341 let _ = Reflect::set(&webapp, &"SecondaryButton".into(), &button);
342
343 let app = TelegramWebApp::instance().unwrap();
344 let params = SecondaryButtonParams {
345 common: BottomButtonParams {
346 text: Some("Next"),
347 ..Default::default()
348 },
349 position: Some(SecondaryButtonPosition::Left)
350 };
351 app.set_secondary_button_params(¶ms).unwrap();
352
353 let stored = received.borrow();
354 assert_eq!(
355 Reflect::get(&stored, &"text".into()).unwrap().as_string(),
356 Some("Next".to_string())
357 );
358 assert_eq!(
359 Reflect::get(&stored, &"position".into())
360 .unwrap()
361 .as_string(),
362 Some("left".to_string())
363 );
364 }
365
366 #[wasm_bindgen_test]
367 #[allow(dead_code, clippy::unused_unit)]
368 fn bottom_button_getters_return_values() {
369 let webapp = setup_webapp();
370 let button = Object::new();
371 let _ = Reflect::set(&button, &"text".into(), &"Label".into());
372 let _ = Reflect::set(&button, &"textColor".into(), &"#111111".into());
373 let _ = Reflect::set(&button, &"color".into(), &"#222222".into());
374 let _ = Reflect::set(&button, &"isVisible".into(), &JsValue::TRUE);
375 let _ = Reflect::set(&button, &"isActive".into(), &JsValue::TRUE);
376 let _ = Reflect::set(&button, &"isProgressVisible".into(), &JsValue::FALSE);
377 let _ = Reflect::set(&button, &"hasShineEffect".into(), &JsValue::TRUE);
378
379 let _ = Reflect::set(&webapp, &"MainButton".into(), &button);
380
381 let app = TelegramWebApp::instance().unwrap();
382 assert_eq!(
383 app.bottom_button_text(BottomButton::Main),
384 Some("Label".into())
385 );
386 assert_eq!(
387 app.bottom_button_text_color(BottomButton::Main),
388 Some("#111111".into())
389 );
390 assert_eq!(
391 app.bottom_button_color(BottomButton::Main),
392 Some("#222222".into())
393 );
394 assert!(app.is_bottom_button_visible(BottomButton::Main));
395 assert!(app.is_bottom_button_active(BottomButton::Main));
396 assert!(!app.is_bottom_button_progress_visible(BottomButton::Main));
397 assert!(app.bottom_button_has_shine_effect(BottomButton::Main));
398 }
399
400 #[wasm_bindgen_test]
401 #[allow(dead_code, clippy::unused_unit)]
402 fn secondary_button_position_is_parsed() {
403 let webapp = setup_webapp();
404 let button = Object::new();
405 let _ = Reflect::set(&button, &"position".into(), &"right".into());
406 let _ = Reflect::set(&webapp, &"SecondaryButton".into(), &button);
407
408 let app = TelegramWebApp::instance().unwrap();
409 assert_eq!(
410 app.secondary_button_position(),
411 Some(SecondaryButtonPosition::Right)
412 );
413 }
414
415 #[wasm_bindgen_test]
416 #[allow(dead_code, clippy::unused_unit)]
417 fn set_header_color_calls_js() {
418 let webapp = setup_webapp();
419 let received = Rc::new(RefCell::new(None));
420 let rc_clone = Rc::clone(&received);
421
422 let cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
423 *rc_clone.borrow_mut() = v.as_string();
424 });
425 let _ = Reflect::set(
426 &webapp,
427 &"setHeaderColor".into(),
428 cb.as_ref().unchecked_ref()
429 );
430 cb.forget();
431
432 let app = TelegramWebApp::instance().unwrap();
433 app.set_header_color("#abcdef").unwrap();
434 assert_eq!(received.borrow().as_deref(), Some("#abcdef"));
435 }
436
437 #[wasm_bindgen_test]
438 #[allow(dead_code, clippy::unused_unit)]
439 fn set_background_color_calls_js() {
440 let webapp = setup_webapp();
441 let received = Rc::new(RefCell::new(None));
442 let rc_clone = Rc::clone(&received);
443
444 let cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
445 *rc_clone.borrow_mut() = v.as_string();
446 });
447 let _ = Reflect::set(
448 &webapp,
449 &"setBackgroundColor".into(),
450 cb.as_ref().unchecked_ref()
451 );
452 cb.forget();
453
454 let app = TelegramWebApp::instance().unwrap();
455 app.set_background_color("#123456").unwrap();
456 assert_eq!(received.borrow().as_deref(), Some("#123456"));
457 }
458
459 #[wasm_bindgen_test]
460 #[allow(dead_code, clippy::unused_unit)]
461 fn set_bottom_bar_color_calls_js() {
462 let webapp = setup_webapp();
463 let received = Rc::new(RefCell::new(None));
464 let rc_clone = Rc::clone(&received);
465
466 let cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
467 *rc_clone.borrow_mut() = v.as_string();
468 });
469 let _ = Reflect::set(
470 &webapp,
471 &"setBottomBarColor".into(),
472 cb.as_ref().unchecked_ref()
473 );
474 cb.forget();
475
476 let app = TelegramWebApp::instance().unwrap();
477 app.set_bottom_bar_color("#654321").unwrap();
478 assert_eq!(received.borrow().as_deref(), Some("#654321"));
479 }
480
481 #[wasm_bindgen_test]
482 #[allow(dead_code, clippy::unused_unit)]
483 fn viewport_dimensions() {
484 let webapp = setup_webapp();
485 let _ = Reflect::set(&webapp, &"viewportWidth".into(), &JsValue::from_f64(320.0));
486 let _ = Reflect::set(
487 &webapp,
488 &"viewportStableHeight".into(),
489 &JsValue::from_f64(480.0)
490 );
491 let app = TelegramWebApp::instance().unwrap();
492 assert_eq!(app.viewport_width(), Some(320.0));
493 assert_eq!(app.viewport_stable_height(), Some(480.0));
494 }
495
496 #[wasm_bindgen_test]
497 #[allow(dead_code, clippy::unused_unit)]
498 fn version_check_invokes_js() {
499 let webapp = setup_webapp();
500 let cb = Function::new_with_args("v", "return v === '9.0';");
501 let _ = Reflect::set(&webapp, &"isVersionAtLeast".into(), &cb);
502
503 let app = TelegramWebApp::instance().unwrap();
504 assert!(app.is_version_at_least("9.0").unwrap());
505 assert!(!app.is_version_at_least("9.1").unwrap());
506 }
507
508 #[wasm_bindgen_test]
509 #[allow(dead_code, clippy::unused_unit)]
510 fn safe_area_insets_are_parsed() {
511 let webapp = setup_webapp();
512 let safe_area = Object::new();
513 let _ = Reflect::set(&safe_area, &"top".into(), &JsValue::from_f64(1.0));
514 let _ = Reflect::set(&safe_area, &"bottom".into(), &JsValue::from_f64(2.0));
515 let _ = Reflect::set(&safe_area, &"left".into(), &JsValue::from_f64(3.0));
516 let _ = Reflect::set(&safe_area, &"right".into(), &JsValue::from_f64(4.0));
517 let _ = Reflect::set(&webapp, &"safeAreaInset".into(), &safe_area);
518
519 let content_safe = Object::new();
520 let _ = Reflect::set(&content_safe, &"top".into(), &JsValue::from_f64(5.0));
521 let _ = Reflect::set(&content_safe, &"bottom".into(), &JsValue::from_f64(6.0));
522 let _ = Reflect::set(&content_safe, &"left".into(), &JsValue::from_f64(7.0));
523 let _ = Reflect::set(&content_safe, &"right".into(), &JsValue::from_f64(8.0));
524 let _ = Reflect::set(&webapp, &"contentSafeAreaInset".into(), &content_safe);
525
526 let app = TelegramWebApp::instance().unwrap();
527 let inset = app.safe_area_inset().expect("safe area");
528 assert_eq!(inset.top, 1.0);
529 assert_eq!(inset.bottom, 2.0);
530 assert_eq!(inset.left, 3.0);
531 assert_eq!(inset.right, 4.0);
532
533 let content = app.content_safe_area_inset().expect("content area");
534 assert_eq!(content.top, 5.0);
535 }
536
537 #[wasm_bindgen_test]
538 #[allow(dead_code, clippy::unused_unit)]
539 fn activity_flags_are_reported() {
540 let webapp = setup_webapp();
541 let _ = Reflect::set(&webapp, &"isActive".into(), &JsValue::TRUE);
542 let _ = Reflect::set(&webapp, &"isFullscreen".into(), &JsValue::TRUE);
543 let _ = Reflect::set(&webapp, &"isOrientationLocked".into(), &JsValue::FALSE);
544 let _ = Reflect::set(&webapp, &"isVerticalSwipesEnabled".into(), &JsValue::TRUE);
545
546 let app = TelegramWebApp::instance().unwrap();
547 assert!(app.is_active());
548 assert!(app.is_fullscreen());
549 assert!(!app.is_orientation_locked());
550 assert!(app.is_vertical_swipes_enabled());
551 }
552
553 #[wasm_bindgen_test]
554 #[allow(dead_code, clippy::unused_unit)]
555 fn back_button_visibility_and_callback() {
556 let webapp = setup_webapp();
557 let back_button = Object::new();
558 let _ = Reflect::set(&webapp, &"BackButton".into(), &back_button);
559 let _ = Reflect::set(&back_button, &"isVisible".into(), &JsValue::TRUE);
560
561 let on_click = Function::new_with_args("cb", "this.cb = cb;");
562 let off_click = Function::new_with_args("", "delete this.cb;");
563 let _ = Reflect::set(&back_button, &"onClick".into(), &on_click);
564 let _ = Reflect::set(&back_button, &"offClick".into(), &off_click);
565
566 let called = Rc::new(Cell::new(false));
567 let called_clone = Rc::clone(&called);
568
569 let app = TelegramWebApp::instance().unwrap();
570 assert!(app.is_back_button_visible());
571 let handle = app
572 .set_back_button_callback(move || {
573 called_clone.set(true);
574 })
575 .unwrap();
576
577 let stored = Reflect::has(&back_button, &"cb".into()).unwrap();
578 assert!(stored);
579
580 let cb_fn = Reflect::get(&back_button, &"cb".into())
581 .unwrap()
582 .dyn_into::<Function>()
583 .unwrap();
584 let _ = cb_fn.call0(&JsValue::NULL);
585 assert!(called.get());
586
587 app.remove_back_button_callback(handle).unwrap();
588 let stored_after = Reflect::has(&back_button, &"cb".into()).unwrap();
589 assert!(!stored_after);
590 }
591
592 #[wasm_bindgen_test]
593 #[allow(dead_code, clippy::unused_unit)]
594 fn bottom_button_callback_register_and_remove() {
595 let webapp = setup_webapp();
596 let main_button = Object::new();
597 let _ = Reflect::set(&webapp, &"MainButton".into(), &main_button);
598
599 let on_click = Function::new_with_args("cb", "this.cb = cb;");
600 let off_click = Function::new_with_args("", "delete this.cb;");
601 let _ = Reflect::set(&main_button, &"onClick".into(), &on_click);
602 let _ = Reflect::set(&main_button, &"offClick".into(), &off_click);
603
604 let called = Rc::new(Cell::new(false));
605 let called_clone = Rc::clone(&called);
606
607 let app = TelegramWebApp::instance().unwrap();
608 let handle = app
609 .set_bottom_button_callback(BottomButton::Main, move || {
610 called_clone.set(true);
611 })
612 .unwrap();
613
614 let stored = Reflect::has(&main_button, &"cb".into()).unwrap();
615 assert!(stored);
616
617 let cb_fn = Reflect::get(&main_button, &"cb".into())
618 .unwrap()
619 .dyn_into::<Function>()
620 .unwrap();
621 let _ = cb_fn.call0(&JsValue::NULL);
622 assert!(called.get());
623
624 app.remove_bottom_button_callback(handle).unwrap();
625 let stored_after = Reflect::has(&main_button, &"cb".into()).unwrap();
626 assert!(!stored_after);
627 }
628
629 #[wasm_bindgen_test]
630 #[allow(dead_code, clippy::unused_unit)]
631 fn secondary_button_callback_register_and_remove() {
632 let webapp = setup_webapp();
633 let secondary_button = Object::new();
634 let _ = Reflect::set(&webapp, &"SecondaryButton".into(), &secondary_button);
635
636 let on_click = Function::new_with_args("cb", "this.cb = cb;");
637 let off_click = Function::new_with_args("", "delete this.cb;");
638 let _ = Reflect::set(&secondary_button, &"onClick".into(), &on_click);
639 let _ = Reflect::set(&secondary_button, &"offClick".into(), &off_click);
640
641 let called = Rc::new(Cell::new(false));
642 let called_clone = Rc::clone(&called);
643
644 let app = TelegramWebApp::instance().unwrap();
645 let handle = app
646 .set_bottom_button_callback(BottomButton::Secondary, move || {
647 called_clone.set(true);
648 })
649 .unwrap();
650
651 let stored = Reflect::has(&secondary_button, &"cb".into()).unwrap();
652 assert!(stored);
653
654 let cb_fn = Reflect::get(&secondary_button, &"cb".into())
655 .unwrap()
656 .dyn_into::<Function>()
657 .unwrap();
658 let _ = cb_fn.call0(&JsValue::NULL);
659 assert!(called.get());
660
661 app.remove_bottom_button_callback(handle).unwrap();
662 let stored_after = Reflect::has(&secondary_button, &"cb".into()).unwrap();
663 assert!(!stored_after);
664 }
665
666 #[wasm_bindgen_test]
667 #[allow(dead_code, clippy::unused_unit)]
668 fn on_event_register_and_remove() {
669 let webapp = setup_webapp();
670 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
671 let off_event = Function::new_with_args("name", "delete this[name];");
672 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
673 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
674
675 let app = TelegramWebApp::instance().unwrap();
676 let handle = app.on_event("test", |_: JsValue| {}).unwrap();
677 assert!(Reflect::has(&webapp, &"test".into()).unwrap());
678 app.off_event(handle).unwrap();
679 assert!(!Reflect::has(&webapp, &"test".into()).unwrap());
680 }
681
682 #[wasm_bindgen_test]
683 #[allow(dead_code, clippy::unused_unit)]
684 fn background_event_register_and_remove() {
685 let webapp = setup_webapp();
686 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
687 let off_event = Function::new_with_args("name", "delete this[name];");
688 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
689 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
690
691 let app = TelegramWebApp::instance().unwrap();
692 let handle = app
693 .on_background_event(BackgroundEvent::MainButtonClicked, |_| {})
694 .unwrap();
695 assert!(Reflect::has(&webapp, &"mainButtonClicked".into()).unwrap());
696 app.off_event(handle).unwrap();
697 assert!(!Reflect::has(&webapp, &"mainButtonClicked".into()).unwrap());
698 }
699
700 #[wasm_bindgen_test]
701 #[allow(dead_code, clippy::unused_unit)]
702 fn background_event_delivers_data() {
703 let webapp = setup_webapp();
704 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
705 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
706
707 let app = TelegramWebApp::instance().unwrap();
708 let received = Rc::new(RefCell::new(String::new()));
709 let received_clone = Rc::clone(&received);
710 let _handle = app
711 .on_background_event(BackgroundEvent::InvoiceClosed, move |v| {
712 *received_clone.borrow_mut() = v.as_string().unwrap_or_default();
713 })
714 .unwrap();
715
716 let cb = Reflect::get(&webapp, &"invoiceClosed".into())
717 .unwrap()
718 .dyn_into::<Function>()
719 .unwrap();
720 let _ = cb.call1(&JsValue::NULL, &JsValue::from_str("paid"));
721 assert_eq!(received.borrow().as_str(), "paid");
722 }
723
724 #[wasm_bindgen_test]
725 #[allow(dead_code, clippy::unused_unit)]
726 fn theme_changed_register_and_remove() {
727 let webapp = setup_webapp();
728 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
729 let off_event = Function::new_with_args("name", "delete this[name];");
730 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
731 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
732
733 let app = TelegramWebApp::instance().unwrap();
734 let handle = app.on_theme_changed(|| {}).unwrap();
735 assert!(Reflect::has(&webapp, &"themeChanged".into()).unwrap());
736 app.off_event(handle).unwrap();
737 assert!(!Reflect::has(&webapp, &"themeChanged".into()).unwrap());
738 }
739
740 #[wasm_bindgen_test]
741 #[allow(dead_code, clippy::unused_unit)]
742 fn safe_area_changed_register_and_remove() {
743 let webapp = setup_webapp();
744 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
745 let off_event = Function::new_with_args("name", "delete this[name];");
746 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
747 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
748
749 let app = TelegramWebApp::instance().unwrap();
750 let handle = app.on_safe_area_changed(|| {}).unwrap();
751 assert!(Reflect::has(&webapp, &"safeAreaChanged".into()).unwrap());
752 app.off_event(handle).unwrap();
753 assert!(!Reflect::has(&webapp, &"safeAreaChanged".into()).unwrap());
754 }
755
756 #[wasm_bindgen_test]
757 #[allow(dead_code, clippy::unused_unit)]
758 fn content_safe_area_changed_register_and_remove() {
759 let webapp = setup_webapp();
760 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
761 let off_event = Function::new_with_args("name", "delete this[name];");
762 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
763 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
764
765 let app = TelegramWebApp::instance().unwrap();
766 let handle = app.on_content_safe_area_changed(|| {}).unwrap();
767 assert!(Reflect::has(&webapp, &"contentSafeAreaChanged".into()).unwrap());
768 app.off_event(handle).unwrap();
769 assert!(!Reflect::has(&webapp, &"contentSafeAreaChanged".into()).unwrap());
770 }
771
772 #[wasm_bindgen_test]
773 #[allow(dead_code, clippy::unused_unit)]
774 fn viewport_changed_register_and_remove() {
775 let webapp = setup_webapp();
776 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
777 let off_event = Function::new_with_args("name", "delete this[name];");
778 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
779 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
780
781 let app = TelegramWebApp::instance().unwrap();
782 let handle = app.on_viewport_changed(|| {}).unwrap();
783 assert!(Reflect::has(&webapp, &"viewportChanged".into()).unwrap());
784 app.off_event(handle).unwrap();
785 assert!(!Reflect::has(&webapp, &"viewportChanged".into()).unwrap());
786 }
787
788 #[wasm_bindgen_test]
789 #[allow(dead_code, clippy::unused_unit)]
790 fn clipboard_text_received_register_and_remove() {
791 let webapp = setup_webapp();
792 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
793 let off_event = Function::new_with_args("name", "delete this[name];");
794 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
795 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
796
797 let app = TelegramWebApp::instance().unwrap();
798 let handle = app.on_clipboard_text_received(|_| {}).unwrap();
799 assert!(Reflect::has(&webapp, &"clipboardTextReceived".into()).unwrap());
800 app.off_event(handle).unwrap();
801 assert!(!Reflect::has(&webapp, &"clipboardTextReceived".into()).unwrap());
802 }
803
804 #[wasm_bindgen_test]
805 #[allow(dead_code, clippy::unused_unit)]
806 fn open_link_and_telegram_link() {
807 let webapp = setup_webapp();
808 let open_link = Function::new_with_args("url", "this.open_link = url;");
809 let open_tg_link = Function::new_with_args("url", "this.open_tg_link = url;");
810 let _ = Reflect::set(&webapp, &"openLink".into(), &open_link);
811 let _ = Reflect::set(&webapp, &"openTelegramLink".into(), &open_tg_link);
812
813 let app = TelegramWebApp::instance().unwrap();
814 let url = "https://example.com";
815 app.open_link(url, None).unwrap();
816 app.open_telegram_link(url).unwrap();
817
818 assert_eq!(
819 Reflect::get(&webapp, &"open_link".into())
820 .unwrap()
821 .as_string()
822 .as_deref(),
823 Some(url)
824 );
825 assert_eq!(
826 Reflect::get(&webapp, &"open_tg_link".into())
827 .unwrap()
828 .as_string()
829 .as_deref(),
830 Some(url)
831 );
832 }
833
834 #[wasm_bindgen_test]
835 #[allow(dead_code, clippy::unused_unit)]
836 fn invoice_closed_register_and_remove() {
837 let webapp = setup_webapp();
838 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
839 let off_event = Function::new_with_args("name", "delete this[name];");
840 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
841 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
842
843 let app = TelegramWebApp::instance().unwrap();
844 let handle = app.on_invoice_closed(|_| {}).unwrap();
845 assert!(Reflect::has(&webapp, &"invoiceClosed".into()).unwrap());
846 app.off_event(handle).unwrap();
847 assert!(!Reflect::has(&webapp, &"invoiceClosed".into()).unwrap());
848 }
849
850 #[wasm_bindgen_test]
851 #[allow(dead_code, clippy::unused_unit)]
852 fn invoice_closed_invokes_callback() {
853 let webapp = setup_webapp();
854 let on_event = Function::new_with_args("name, cb", "this.cb = cb;");
855 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
856
857 let app = TelegramWebApp::instance().unwrap();
858 let status = Rc::new(RefCell::new(String::new()));
859 let status_clone = Rc::clone(&status);
860 app.on_invoice_closed(move |s| {
861 *status_clone.borrow_mut() = s;
862 })
863 .unwrap();
864
865 let cb = Reflect::get(&webapp, &"cb".into())
866 .unwrap()
867 .dyn_into::<Function>()
868 .unwrap();
869 cb.call1(&webapp, &"paid".into()).unwrap();
870 assert_eq!(status.borrow().as_str(), "paid");
871 cb.call1(&webapp, &"failed".into()).unwrap();
872 assert_eq!(status.borrow().as_str(), "failed");
873 }
874
875 #[wasm_bindgen_test]
876 #[allow(dead_code, clippy::unused_unit)]
877 fn open_invoice_invokes_callback() {
878 let webapp = setup_webapp();
879 let open_invoice = Function::new_with_args("url, cb", "cb('paid');");
880 let _ = Reflect::set(&webapp, &"openInvoice".into(), &open_invoice);
881
882 let app = TelegramWebApp::instance().unwrap();
883 let status = Rc::new(RefCell::new(String::new()));
884 let status_clone = Rc::clone(&status);
885
886 app.open_invoice("https://invoice", move |s| {
887 *status_clone.borrow_mut() = s;
888 })
889 .unwrap();
890
891 assert_eq!(status.borrow().as_str(), "paid");
892 }
893
894 #[wasm_bindgen_test]
895 #[allow(dead_code, clippy::unused_unit)]
896 fn switch_inline_query_calls_js() {
897 let webapp = setup_webapp();
898 let switch_inline =
899 Function::new_with_args("query, types", "this.query = query; this.types = types;");
900 let _ = Reflect::set(&webapp, &"switchInlineQuery".into(), &switch_inline);
901
902 let app = TelegramWebApp::instance().unwrap();
903 let types = JsValue::from_str("users");
904 app.switch_inline_query("search", Some(&types)).unwrap();
905
906 assert_eq!(
907 Reflect::get(&webapp, &"query".into())
908 .unwrap()
909 .as_string()
910 .as_deref(),
911 Some("search"),
912 );
913 assert_eq!(
914 Reflect::get(&webapp, &"types".into())
915 .unwrap()
916 .as_string()
917 .as_deref(),
918 Some("users"),
919 );
920 }
921
922 #[wasm_bindgen_test]
923 #[allow(dead_code, clippy::unused_unit)]
924 fn share_message_calls_js() {
925 let webapp = setup_webapp();
926 let share = Function::new_with_args("id, cb", "this.shared_id = id; cb(true);");
927 let _ = Reflect::set(&webapp, &"shareMessage".into(), &share);
928
929 let app = TelegramWebApp::instance().unwrap();
930 let sent = Rc::new(Cell::new(false));
931 let sent_clone = Rc::clone(&sent);
932
933 app.share_message("123", move |s| {
934 sent_clone.set(s);
935 })
936 .unwrap();
937
938 assert_eq!(
939 Reflect::get(&webapp, &"shared_id".into())
940 .unwrap()
941 .as_string()
942 .as_deref(),
943 Some("123"),
944 );
945 assert!(sent.get());
946 }
947
948 #[wasm_bindgen_test]
949 #[allow(dead_code, clippy::unused_unit)]
950 fn share_to_story_calls_js() {
951 let webapp = setup_webapp();
952 let share = Function::new_with_args(
953 "url, params",
954 "this.story_url = url; this.story_params = params;"
955 );
956 let _ = Reflect::set(&webapp, &"shareToStory".into(), &share);
957
958 let app = TelegramWebApp::instance().unwrap();
959 let url = "https://example.com/media";
960 let params = Object::new();
961 let _ = Reflect::set(¶ms, &"text".into(), &"hi".into());
962 app.share_to_story(url, Some(¶ms.into())).unwrap();
963
964 assert_eq!(
965 Reflect::get(&webapp, &"story_url".into())
966 .unwrap()
967 .as_string()
968 .as_deref(),
969 Some(url),
970 );
971 let stored = Reflect::get(&webapp, &"story_params".into()).unwrap();
972 assert_eq!(
973 Reflect::get(&stored, &"text".into())
974 .unwrap()
975 .as_string()
976 .as_deref(),
977 Some("hi"),
978 );
979 }
980
981 #[wasm_bindgen_test]
982 #[allow(dead_code, clippy::unused_unit)]
983 fn share_url_calls_js() {
984 let webapp = setup_webapp();
985 let share = Function::new_with_args(
986 "url, text",
987 "this.shared_url = url; this.shared_text = text;"
988 );
989 let _ = Reflect::set(&webapp, &"shareURL".into(), &share);
990
991 let app = TelegramWebApp::instance().unwrap();
992 let url = "https://example.com";
993 let text = "check";
994 app.share_url(url, Some(text)).unwrap();
995
996 assert_eq!(
997 Reflect::get(&webapp, &"shared_url".into())
998 .unwrap()
999 .as_string()
1000 .as_deref(),
1001 Some(url),
1002 );
1003 assert_eq!(
1004 Reflect::get(&webapp, &"shared_text".into())
1005 .unwrap()
1006 .as_string()
1007 .as_deref(),
1008 Some(text),
1009 );
1010 }
1011
1012 #[wasm_bindgen_test]
1013 #[allow(dead_code, clippy::unused_unit)]
1014 fn join_voice_chat_calls_js() {
1015 let webapp = setup_webapp();
1016 let join = Function::new_with_args(
1017 "id, hash",
1018 "this.voice_chat_id = id; this.voice_chat_hash = hash;"
1019 );
1020 let _ = Reflect::set(&webapp, &"joinVoiceChat".into(), &join);
1021
1022 let app = TelegramWebApp::instance().unwrap();
1023 app.join_voice_chat("123", Some("hash")).unwrap();
1024
1025 assert_eq!(
1026 Reflect::get(&webapp, &"voice_chat_id".into())
1027 .unwrap()
1028 .as_string()
1029 .as_deref(),
1030 Some("123"),
1031 );
1032 assert_eq!(
1033 Reflect::get(&webapp, &"voice_chat_hash".into())
1034 .unwrap()
1035 .as_string()
1036 .as_deref(),
1037 Some("hash"),
1038 );
1039 }
1040
1041 #[wasm_bindgen_test]
1042 #[allow(dead_code, clippy::unused_unit)]
1043 fn add_to_home_screen_calls_js() {
1044 let webapp = setup_webapp();
1045 let add = Function::new_with_args("", "this.called = true; return true;");
1046 let _ = Reflect::set(&webapp, &"addToHomeScreen".into(), &add);
1047
1048 let app = TelegramWebApp::instance().unwrap();
1049 let shown = app.add_to_home_screen().unwrap();
1050 assert!(shown);
1051 let called = Reflect::get(&webapp, &"called".into())
1052 .unwrap()
1053 .as_bool()
1054 .unwrap_or(false);
1055 assert!(called);
1056 }
1057
1058 #[wasm_bindgen_test]
1059 #[allow(dead_code, clippy::unused_unit)]
1060 fn request_fullscreen_calls_js() {
1061 let webapp = setup_webapp();
1062 let called = Rc::new(Cell::new(false));
1063 let called_clone = Rc::clone(&called);
1064
1065 let cb = Closure::<dyn FnMut()>::new(move || {
1066 called_clone.set(true);
1067 });
1068 let _ = Reflect::set(
1069 &webapp,
1070 &"requestFullscreen".into(),
1071 cb.as_ref().unchecked_ref()
1072 );
1073 cb.forget();
1074
1075 let app = TelegramWebApp::instance().unwrap();
1076 app.request_fullscreen().unwrap();
1077 assert!(called.get());
1078 }
1079
1080 #[wasm_bindgen_test]
1081 #[allow(dead_code, clippy::unused_unit)]
1082 fn exit_fullscreen_calls_js() {
1083 let webapp = setup_webapp();
1084 let called = Rc::new(Cell::new(false));
1085 let called_clone = Rc::clone(&called);
1086
1087 let cb = Closure::<dyn FnMut()>::new(move || {
1088 called_clone.set(true);
1089 });
1090 let _ = Reflect::set(
1091 &webapp,
1092 &"exitFullscreen".into(),
1093 cb.as_ref().unchecked_ref()
1094 );
1095 cb.forget();
1096
1097 let app = TelegramWebApp::instance().unwrap();
1098 app.exit_fullscreen().unwrap();
1099 assert!(called.get());
1100 }
1101
1102 #[wasm_bindgen_test]
1103 #[allow(dead_code, clippy::unused_unit)]
1104 fn check_home_screen_status_invokes_callback() {
1105 let webapp = setup_webapp();
1106 let check = Function::new_with_args("cb", "cb('added');");
1107 let _ = Reflect::set(&webapp, &"checkHomeScreenStatus".into(), &check);
1108
1109 let app = TelegramWebApp::instance().unwrap();
1110 let status = Rc::new(RefCell::new(String::new()));
1111 let status_clone = Rc::clone(&status);
1112
1113 app.check_home_screen_status(move |s| {
1114 *status_clone.borrow_mut() = s;
1115 })
1116 .unwrap();
1117
1118 assert_eq!(status.borrow().as_str(), "added");
1119 }
1120
1121 #[wasm_bindgen_test]
1122 #[allow(dead_code, clippy::unused_unit)]
1123 fn lock_orientation_calls_js() {
1124 let webapp = setup_webapp();
1125 let received = Rc::new(RefCell::new(None));
1126 let rc_clone = Rc::clone(&received);
1127
1128 let cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
1129 *rc_clone.borrow_mut() = v.as_string();
1130 });
1131 let _ = Reflect::set(
1132 &webapp,
1133 &"lockOrientation".into(),
1134 cb.as_ref().unchecked_ref()
1135 );
1136 cb.forget();
1137
1138 let app = TelegramWebApp::instance().unwrap();
1139 app.lock_orientation("portrait").unwrap();
1140 assert_eq!(received.borrow().as_deref(), Some("portrait"));
1141 }
1142
1143 #[wasm_bindgen_test]
1144 #[allow(dead_code, clippy::unused_unit)]
1145 fn unlock_orientation_calls_js() {
1146 let webapp = setup_webapp();
1147 let called = Rc::new(Cell::new(false));
1148 let called_clone = Rc::clone(&called);
1149
1150 let cb = Closure::<dyn FnMut()>::new(move || {
1151 called_clone.set(true);
1152 });
1153 let _ = Reflect::set(
1154 &webapp,
1155 &"unlockOrientation".into(),
1156 cb.as_ref().unchecked_ref()
1157 );
1158 cb.forget();
1159
1160 let app = TelegramWebApp::instance().unwrap();
1161 app.unlock_orientation().unwrap();
1162 assert!(called.get());
1163 }
1164
1165 #[wasm_bindgen_test]
1166 #[allow(dead_code, clippy::unused_unit)]
1167 fn enable_vertical_swipes_calls_js() {
1168 let webapp = setup_webapp();
1169 let called = Rc::new(Cell::new(false));
1170 let called_clone = Rc::clone(&called);
1171
1172 let cb = Closure::<dyn FnMut()>::new(move || {
1173 called_clone.set(true);
1174 });
1175 let _ = Reflect::set(
1176 &webapp,
1177 &"enableVerticalSwipes".into(),
1178 cb.as_ref().unchecked_ref()
1179 );
1180 cb.forget();
1181
1182 let app = TelegramWebApp::instance().unwrap();
1183 app.enable_vertical_swipes().unwrap();
1184 assert!(called.get());
1185 }
1186
1187 #[wasm_bindgen_test]
1188 #[allow(dead_code, clippy::unused_unit)]
1189 fn disable_vertical_swipes_calls_js() {
1190 let webapp = setup_webapp();
1191 let called = Rc::new(Cell::new(false));
1192 let called_clone = Rc::clone(&called);
1193
1194 let cb = Closure::<dyn FnMut()>::new(move || {
1195 called_clone.set(true);
1196 });
1197 let _ = Reflect::set(
1198 &webapp,
1199 &"disableVerticalSwipes".into(),
1200 cb.as_ref().unchecked_ref()
1201 );
1202 cb.forget();
1203
1204 let app = TelegramWebApp::instance().unwrap();
1205 app.disable_vertical_swipes().unwrap();
1206 assert!(called.get());
1207 }
1208
1209 #[wasm_bindgen_test]
1210 #[allow(dead_code, clippy::unused_unit)]
1211 fn request_write_access_invokes_callback() {
1212 let webapp = setup_webapp();
1213 let request = Function::new_with_args("cb", "cb(true);");
1214 let _ = Reflect::set(&webapp, &"requestWriteAccess".into(), &request);
1215
1216 let app = TelegramWebApp::instance().unwrap();
1217 let granted = Rc::new(Cell::new(false));
1218 let granted_clone = Rc::clone(&granted);
1219
1220 let res = app.request_write_access(move |g| {
1221 granted_clone.set(g);
1222 });
1223 assert!(res.is_ok());
1224
1225 assert!(granted.get());
1226 }
1227
1228 #[wasm_bindgen_test]
1229 #[allow(dead_code, clippy::unused_unit)]
1230 fn download_file_invokes_callback() {
1231 let webapp = setup_webapp();
1232 let received_url = Rc::new(RefCell::new(String::new()));
1233 let received_name = Rc::new(RefCell::new(String::new()));
1234 let url_clone = Rc::clone(&received_url);
1235 let name_clone = Rc::clone(&received_name);
1236
1237 let download = Closure::<dyn FnMut(JsValue, JsValue)>::new(move |params, cb: JsValue| {
1238 let url = Reflect::get(¶ms, &"url".into())
1239 .unwrap()
1240 .as_string()
1241 .unwrap_or_default();
1242 let name = Reflect::get(¶ms, &"file_name".into())
1243 .unwrap()
1244 .as_string()
1245 .unwrap_or_default();
1246 *url_clone.borrow_mut() = url;
1247 *name_clone.borrow_mut() = name;
1248 let func = cb.dyn_ref::<Function>().unwrap();
1249 let _ = func.call1(&JsValue::NULL, &JsValue::from_str("id"));
1250 });
1251 let _ = Reflect::set(
1252 &webapp,
1253 &"downloadFile".into(),
1254 download.as_ref().unchecked_ref()
1255 );
1256 download.forget();
1257
1258 let app = TelegramWebApp::instance().unwrap();
1259 let result = Rc::new(RefCell::new(String::new()));
1260 let result_clone = Rc::clone(&result);
1261 let params = DownloadFileParams {
1262 url: "https://example.com/data.bin",
1263 file_name: Some("data.bin"),
1264 mime_type: None
1265 };
1266 app.download_file(params, move |id| {
1267 *result_clone.borrow_mut() = id;
1268 })
1269 .unwrap();
1270
1271 assert_eq!(
1272 received_url.borrow().as_str(),
1273 "https://example.com/data.bin"
1274 );
1275 assert_eq!(received_name.borrow().as_str(), "data.bin");
1276 assert_eq!(result.borrow().as_str(), "id");
1277 }
1278
1279 #[wasm_bindgen_test]
1280 #[allow(dead_code, clippy::unused_unit)]
1281 fn request_write_access_returns_error_when_missing() {
1282 let _webapp = setup_webapp();
1283 let app = TelegramWebApp::instance().unwrap();
1284 let res = app.request_write_access(|_| {});
1285 assert!(res.is_err());
1286 }
1287 #[wasm_bindgen_test]
1288 #[allow(dead_code, clippy::unused_unit)]
1289 fn request_emoji_status_access_invokes_callback() {
1290 let webapp = setup_webapp();
1291 let request = Function::new_with_args("cb", "cb(false);");
1292 let _ = Reflect::set(&webapp, &"requestEmojiStatusAccess".into(), &request);
1293
1294 let app = TelegramWebApp::instance().unwrap();
1295 let granted = Rc::new(Cell::new(true));
1296 let granted_clone = Rc::clone(&granted);
1297
1298 app.request_emoji_status_access(move |g| {
1299 granted_clone.set(g);
1300 })
1301 .unwrap();
1302
1303 assert!(!granted.get());
1304 }
1305
1306 #[wasm_bindgen_test]
1307 #[allow(dead_code, clippy::unused_unit)]
1308 fn set_emoji_status_invokes_callback() {
1309 let webapp = setup_webapp();
1310 let set_status = Function::new_with_args("status, cb", "this.st = status; cb(true);");
1311 let _ = Reflect::set(&webapp, &"setEmojiStatus".into(), &set_status);
1312
1313 let status = Object::new();
1314 let _ = Reflect::set(
1315 &status,
1316 &"custom_emoji_id".into(),
1317 &JsValue::from_str("321")
1318 );
1319
1320 let app = TelegramWebApp::instance().unwrap();
1321 let success = Rc::new(Cell::new(false));
1322 let success_clone = Rc::clone(&success);
1323
1324 app.set_emoji_status(&status.into(), move |s| {
1325 success_clone.set(s);
1326 })
1327 .unwrap();
1328
1329 assert!(success.get());
1330 let stored = Reflect::get(&webapp, &"st".into()).unwrap();
1331 let id = Reflect::get(&stored, &"custom_emoji_id".into())
1332 .unwrap()
1333 .as_string();
1334 assert_eq!(id.as_deref(), Some("321"));
1335 }
1336
1337 #[wasm_bindgen_test]
1338 #[allow(dead_code, clippy::unused_unit)]
1339 fn show_popup_invokes_callback() {
1340 let webapp = setup_webapp();
1341 let show_popup = Function::new_with_args("params, cb", "cb('ok');");
1342 let _ = Reflect::set(&webapp, &"showPopup".into(), &show_popup);
1343
1344 let app = TelegramWebApp::instance().unwrap();
1345 let button = Rc::new(RefCell::new(String::new()));
1346 let button_clone = Rc::clone(&button);
1347
1348 app.show_popup(&JsValue::NULL, move |id| {
1349 *button_clone.borrow_mut() = id;
1350 })
1351 .unwrap();
1352
1353 assert_eq!(button.borrow().as_str(), "ok");
1354 }
1355
1356 #[wasm_bindgen_test]
1357 #[allow(dead_code, clippy::unused_unit)]
1358 fn read_text_from_clipboard_invokes_callback() {
1359 let webapp = setup_webapp();
1360 let read_clip = Function::new_with_args("cb", "cb('clip');");
1361 let _ = Reflect::set(&webapp, &"readTextFromClipboard".into(), &read_clip);
1362
1363 let app = TelegramWebApp::instance().unwrap();
1364 let text = Rc::new(RefCell::new(String::new()));
1365 let text_clone = Rc::clone(&text);
1366
1367 app.read_text_from_clipboard(move |t| {
1368 *text_clone.borrow_mut() = t;
1369 })
1370 .unwrap();
1371
1372 assert_eq!(text.borrow().as_str(), "clip");
1373 }
1374
1375 #[wasm_bindgen_test]
1376 #[allow(dead_code, clippy::unused_unit)]
1377 fn scan_qr_popup_invokes_callback_and_close() {
1378 let webapp = setup_webapp();
1379 let show_scan = Function::new_with_args("text, cb", "cb('code');");
1380 let close_scan = Function::new_with_args("", "this.closed = true;");
1381 let _ = Reflect::set(&webapp, &"showScanQrPopup".into(), &show_scan);
1382 let _ = Reflect::set(&webapp, &"closeScanQrPopup".into(), &close_scan);
1383
1384 let app = TelegramWebApp::instance().unwrap();
1385 let text = Rc::new(RefCell::new(String::new()));
1386 let text_clone = Rc::clone(&text);
1387
1388 app.show_scan_qr_popup("scan", move |value| {
1389 *text_clone.borrow_mut() = value;
1390 })
1391 .unwrap();
1392 assert_eq!(text.borrow().as_str(), "code");
1393
1394 app.close_scan_qr_popup().unwrap();
1395 let closed = Reflect::get(&webapp, &"closed".into())
1396 .unwrap()
1397 .as_bool()
1398 .unwrap_or(false);
1399 assert!(closed);
1400 }
1401}