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