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 ..Default::default()
306 };
307 app.set_bottom_button_params(BottomButton::Main, ¶ms)
308 .unwrap();
309
310 let stored = received.borrow();
311 assert_eq!(
312 Reflect::get(&stored, &"text".into()).unwrap().as_string(),
313 Some("Send".to_string())
314 );
315 assert_eq!(
316 Reflect::get(&stored, &"color".into()).unwrap().as_string(),
317 Some("#ffffff".to_string())
318 );
319 assert_eq!(
320 Reflect::get(&stored, &"text_color".into())
321 .unwrap()
322 .as_string(),
323 Some("#000000".to_string())
324 );
325 }
326
327 #[wasm_bindgen_test]
328 #[allow(dead_code, clippy::unused_unit)]
329 fn set_secondary_button_params_serializes_position() {
330 let webapp = setup_webapp();
331 let button = Object::new();
332 let received = Rc::new(RefCell::new(Object::new()));
333 let rc_clone = Rc::clone(&received);
334
335 let cb = Closure::<dyn FnMut(JsValue)>::new(move |value: JsValue| {
336 let obj = value.dyn_into::<Object>().expect("object");
337 rc_clone.replace(obj);
338 });
339 let _ = Reflect::set(&button, &"setParams".into(), cb.as_ref().unchecked_ref());
340 cb.forget();
341
342 let _ = Reflect::set(&webapp, &"SecondaryButton".into(), &button);
343
344 let app = TelegramWebApp::instance().unwrap();
345 let params = SecondaryButtonParams {
346 common: BottomButtonParams {
347 text: Some("Next"),
348 ..Default::default()
349 },
350 position: Some(SecondaryButtonPosition::Left)
351 };
352 app.set_secondary_button_params(¶ms).unwrap();
353
354 let stored = received.borrow();
355 assert_eq!(
356 Reflect::get(&stored, &"text".into()).unwrap().as_string(),
357 Some("Next".to_string())
358 );
359 assert_eq!(
360 Reflect::get(&stored, &"position".into())
361 .unwrap()
362 .as_string(),
363 Some("left".to_string())
364 );
365 }
366
367 #[wasm_bindgen_test]
368 #[allow(dead_code, clippy::unused_unit)]
369 fn bottom_button_getters_return_values() {
370 let webapp = setup_webapp();
371 let button = Object::new();
372 let _ = Reflect::set(&button, &"text".into(), &"Label".into());
373 let _ = Reflect::set(&button, &"textColor".into(), &"#111111".into());
374 let _ = Reflect::set(&button, &"color".into(), &"#222222".into());
375 let _ = Reflect::set(&button, &"isVisible".into(), &JsValue::TRUE);
376 let _ = Reflect::set(&button, &"isActive".into(), &JsValue::TRUE);
377 let _ = Reflect::set(&button, &"isProgressVisible".into(), &JsValue::FALSE);
378 let _ = Reflect::set(&button, &"hasShineEffect".into(), &JsValue::TRUE);
379
380 let _ = Reflect::set(&webapp, &"MainButton".into(), &button);
381
382 let app = TelegramWebApp::instance().unwrap();
383 assert_eq!(
384 app.bottom_button_text(BottomButton::Main),
385 Some("Label".into())
386 );
387 assert_eq!(
388 app.bottom_button_text_color(BottomButton::Main),
389 Some("#111111".into())
390 );
391 assert_eq!(
392 app.bottom_button_color(BottomButton::Main),
393 Some("#222222".into())
394 );
395 assert!(app.is_bottom_button_visible(BottomButton::Main));
396 assert!(app.is_bottom_button_active(BottomButton::Main));
397 assert!(!app.is_bottom_button_progress_visible(BottomButton::Main));
398 assert!(app.bottom_button_has_shine_effect(BottomButton::Main));
399 }
400
401 #[wasm_bindgen_test]
402 #[allow(dead_code, clippy::unused_unit)]
403 fn set_bottom_button_icon_custom_emoji_id_calls_js() {
404 let webapp = setup_webapp();
405 let button = Object::new();
406 let received = Rc::new(RefCell::new(None));
407 let rc_clone = Rc::clone(&received);
408
409 let set_icon_cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
410 *rc_clone.borrow_mut() = v.as_string();
411 });
412 let _ = Reflect::set(
413 &button,
414 &"setIconCustomEmojiId".into(),
415 set_icon_cb.as_ref().unchecked_ref()
416 );
417 set_icon_cb.forget();
418
419 let _ = Reflect::set(&webapp, &"MainButton".into(), &button);
420
421 let app = TelegramWebApp::instance().unwrap();
422 app.set_bottom_button_icon_custom_emoji_id(BottomButton::Main, "123456789")
423 .unwrap();
424 assert_eq!(received.borrow().as_deref(), Some("123456789"));
425 }
426
427 #[wasm_bindgen_test]
428 #[allow(dead_code, clippy::unused_unit)]
429 fn set_secondary_button_icon_custom_emoji_id_calls_js() {
430 let webapp = setup_webapp();
431 let secondary_button = Object::new();
432 let received = Rc::new(RefCell::new(None));
433 let rc_clone = Rc::clone(&received);
434
435 let set_icon_cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
436 *rc_clone.borrow_mut() = v.as_string();
437 });
438 let _ = Reflect::set(
439 &secondary_button,
440 &"setIconCustomEmojiId".into(),
441 set_icon_cb.as_ref().unchecked_ref()
442 );
443 set_icon_cb.forget();
444
445 let _ = Reflect::set(&webapp, &"SecondaryButton".into(), &secondary_button);
446
447 let app = TelegramWebApp::instance().unwrap();
448 app.set_bottom_button_icon_custom_emoji_id(BottomButton::Secondary, "987654321")
449 .unwrap();
450 assert_eq!(received.borrow().as_deref(), Some("987654321"));
451 }
452
453 #[wasm_bindgen_test]
454 #[allow(dead_code, clippy::unused_unit)]
455 fn bottom_button_icon_custom_emoji_id_getter_returns_value() {
456 let webapp = setup_webapp();
457 let button = Object::new();
458 let _ = Reflect::set(&button, &"iconCustomEmojiId".into(), &"123456789".into());
459
460 let _ = Reflect::set(&webapp, &"MainButton".into(), &button);
461
462 let app = TelegramWebApp::instance().unwrap();
463 assert_eq!(
464 app.bottom_button_icon_custom_emoji_id(BottomButton::Main),
465 Some("123456789".into())
466 );
467 }
468
469 #[wasm_bindgen_test]
470 #[allow(dead_code, clippy::unused_unit)]
471 fn bottom_button_icon_custom_emoji_id_returns_none_when_not_set() {
472 let webapp = setup_webapp();
473 let button = Object::new();
474
475 let _ = Reflect::set(&webapp, &"MainButton".into(), &button);
476
477 let app = TelegramWebApp::instance().unwrap();
478 assert_eq!(
479 app.bottom_button_icon_custom_emoji_id(BottomButton::Main),
480 None
481 );
482 }
483
484 #[wasm_bindgen_test]
485 #[allow(dead_code, clippy::unused_unit)]
486 fn set_bottom_button_params_with_icon_custom_emoji_id() {
487 let webapp = setup_webapp();
488 let button = Object::new();
489 let received = Rc::new(RefCell::new(Object::new()));
490 let rc_clone = Rc::clone(&received);
491
492 let cb = Closure::<dyn FnMut(JsValue)>::new(move |value: JsValue| {
493 let obj = value.dyn_into::<Object>().expect("object");
494 rc_clone.replace(obj);
495 });
496 let _ = Reflect::set(&button, &"setParams".into(), cb.as_ref().unchecked_ref());
497 cb.forget();
498
499 let _ = Reflect::set(&webapp, &"MainButton".into(), &button);
500
501 let app = TelegramWebApp::instance().unwrap();
502 let params = BottomButtonParams {
503 text: Some("Send"),
504 icon_custom_emoji_id: Some("123456789"),
505 ..Default::default()
506 };
507 app.set_bottom_button_params(BottomButton::Main, ¶ms)
508 .unwrap();
509
510 let stored = received.borrow();
511 assert_eq!(
512 Reflect::get(&stored, &"text".into()).unwrap().as_string(),
513 Some("Send".to_string())
514 );
515 assert_eq!(
516 Reflect::get(&stored, &"icon_custom_emoji_id".into())
517 .unwrap()
518 .as_string(),
519 Some("123456789".to_string())
520 );
521 }
522
523 #[wasm_bindgen_test]
524 #[allow(dead_code, clippy::unused_unit)]
525 fn secondary_button_position_is_parsed() {
526 let webapp = setup_webapp();
527 let button = Object::new();
528 let _ = Reflect::set(&button, &"position".into(), &"right".into());
529 let _ = Reflect::set(&webapp, &"SecondaryButton".into(), &button);
530
531 let app = TelegramWebApp::instance().unwrap();
532 assert_eq!(
533 app.secondary_button_position(),
534 Some(SecondaryButtonPosition::Right)
535 );
536 }
537
538 #[wasm_bindgen_test]
539 #[allow(dead_code, clippy::unused_unit)]
540 fn set_header_color_calls_js() {
541 let webapp = setup_webapp();
542 let received = Rc::new(RefCell::new(None));
543 let rc_clone = Rc::clone(&received);
544
545 let cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
546 *rc_clone.borrow_mut() = v.as_string();
547 });
548 let _ = Reflect::set(
549 &webapp,
550 &"setHeaderColor".into(),
551 cb.as_ref().unchecked_ref()
552 );
553 cb.forget();
554
555 let app = TelegramWebApp::instance().unwrap();
556 app.set_header_color("#abcdef").unwrap();
557 assert_eq!(received.borrow().as_deref(), Some("#abcdef"));
558 }
559
560 #[wasm_bindgen_test]
561 #[allow(dead_code, clippy::unused_unit)]
562 fn set_background_color_calls_js() {
563 let webapp = setup_webapp();
564 let received = Rc::new(RefCell::new(None));
565 let rc_clone = Rc::clone(&received);
566
567 let cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
568 *rc_clone.borrow_mut() = v.as_string();
569 });
570 let _ = Reflect::set(
571 &webapp,
572 &"setBackgroundColor".into(),
573 cb.as_ref().unchecked_ref()
574 );
575 cb.forget();
576
577 let app = TelegramWebApp::instance().unwrap();
578 app.set_background_color("#123456").unwrap();
579 assert_eq!(received.borrow().as_deref(), Some("#123456"));
580 }
581
582 #[wasm_bindgen_test]
583 #[allow(dead_code, clippy::unused_unit)]
584 fn set_bottom_bar_color_calls_js() {
585 let webapp = setup_webapp();
586 let received = Rc::new(RefCell::new(None));
587 let rc_clone = Rc::clone(&received);
588
589 let cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
590 *rc_clone.borrow_mut() = v.as_string();
591 });
592 let _ = Reflect::set(
593 &webapp,
594 &"setBottomBarColor".into(),
595 cb.as_ref().unchecked_ref()
596 );
597 cb.forget();
598
599 let app = TelegramWebApp::instance().unwrap();
600 app.set_bottom_bar_color("#654321").unwrap();
601 assert_eq!(received.borrow().as_deref(), Some("#654321"));
602 }
603
604 #[wasm_bindgen_test]
605 #[allow(dead_code, clippy::unused_unit)]
606 fn viewport_dimensions() {
607 let webapp = setup_webapp();
608 let _ = Reflect::set(&webapp, &"viewportWidth".into(), &JsValue::from_f64(320.0));
609 let _ = Reflect::set(
610 &webapp,
611 &"viewportStableHeight".into(),
612 &JsValue::from_f64(480.0)
613 );
614 let app = TelegramWebApp::instance().unwrap();
615 assert_eq!(app.viewport_width(), Some(320.0));
616 assert_eq!(app.viewport_stable_height(), Some(480.0));
617 }
618
619 #[wasm_bindgen_test]
620 #[allow(dead_code, clippy::unused_unit)]
621 fn version_check_invokes_js() {
622 let webapp = setup_webapp();
623 let cb = Function::new_with_args("v", "return v === '9.0';");
624 let _ = Reflect::set(&webapp, &"isVersionAtLeast".into(), &cb);
625
626 let app = TelegramWebApp::instance().unwrap();
627 assert!(app.is_version_at_least("9.0").unwrap());
628 assert!(!app.is_version_at_least("9.1").unwrap());
629 }
630
631 #[wasm_bindgen_test]
632 #[allow(dead_code, clippy::unused_unit)]
633 fn safe_area_insets_are_parsed() {
634 let webapp = setup_webapp();
635 let safe_area = Object::new();
636 let _ = Reflect::set(&safe_area, &"top".into(), &JsValue::from_f64(1.0));
637 let _ = Reflect::set(&safe_area, &"bottom".into(), &JsValue::from_f64(2.0));
638 let _ = Reflect::set(&safe_area, &"left".into(), &JsValue::from_f64(3.0));
639 let _ = Reflect::set(&safe_area, &"right".into(), &JsValue::from_f64(4.0));
640 let _ = Reflect::set(&webapp, &"safeAreaInset".into(), &safe_area);
641
642 let content_safe = Object::new();
643 let _ = Reflect::set(&content_safe, &"top".into(), &JsValue::from_f64(5.0));
644 let _ = Reflect::set(&content_safe, &"bottom".into(), &JsValue::from_f64(6.0));
645 let _ = Reflect::set(&content_safe, &"left".into(), &JsValue::from_f64(7.0));
646 let _ = Reflect::set(&content_safe, &"right".into(), &JsValue::from_f64(8.0));
647 let _ = Reflect::set(&webapp, &"contentSafeAreaInset".into(), &content_safe);
648
649 let app = TelegramWebApp::instance().unwrap();
650 let inset = app.safe_area_inset().expect("safe area");
651 assert_eq!(inset.top, 1.0);
652 assert_eq!(inset.bottom, 2.0);
653 assert_eq!(inset.left, 3.0);
654 assert_eq!(inset.right, 4.0);
655
656 let content = app.content_safe_area_inset().expect("content area");
657 assert_eq!(content.top, 5.0);
658 }
659
660 #[wasm_bindgen_test]
661 #[allow(dead_code, clippy::unused_unit)]
662 fn activity_flags_are_reported() {
663 let webapp = setup_webapp();
664 let _ = Reflect::set(&webapp, &"isActive".into(), &JsValue::TRUE);
665 let _ = Reflect::set(&webapp, &"isFullscreen".into(), &JsValue::TRUE);
666 let _ = Reflect::set(&webapp, &"isOrientationLocked".into(), &JsValue::FALSE);
667 let _ = Reflect::set(&webapp, &"isVerticalSwipesEnabled".into(), &JsValue::TRUE);
668
669 let app = TelegramWebApp::instance().unwrap();
670 assert!(app.is_active());
671 assert!(app.is_fullscreen());
672 assert!(!app.is_orientation_locked());
673 assert!(app.is_vertical_swipes_enabled());
674 }
675
676 #[wasm_bindgen_test]
677 #[allow(dead_code, clippy::unused_unit)]
678 fn back_button_visibility_and_callback() {
679 let webapp = setup_webapp();
680 let back_button = Object::new();
681 let _ = Reflect::set(&webapp, &"BackButton".into(), &back_button);
682 let _ = Reflect::set(&back_button, &"isVisible".into(), &JsValue::TRUE);
683
684 let on_click = Function::new_with_args("cb", "this.cb = cb;");
685 let off_click = Function::new_with_args("", "delete this.cb;");
686 let _ = Reflect::set(&back_button, &"onClick".into(), &on_click);
687 let _ = Reflect::set(&back_button, &"offClick".into(), &off_click);
688
689 let called = Rc::new(Cell::new(false));
690 let called_clone = Rc::clone(&called);
691
692 let app = TelegramWebApp::instance().unwrap();
693 assert!(app.is_back_button_visible());
694 let handle = app
695 .set_back_button_callback(move || {
696 called_clone.set(true);
697 })
698 .unwrap();
699
700 let stored = Reflect::has(&back_button, &"cb".into()).unwrap();
701 assert!(stored);
702
703 let cb_fn = Reflect::get(&back_button, &"cb".into())
704 .unwrap()
705 .dyn_into::<Function>()
706 .unwrap();
707 let _ = cb_fn.call0(&JsValue::NULL);
708 assert!(called.get());
709
710 app.remove_back_button_callback(handle).unwrap();
711 let stored_after = Reflect::has(&back_button, &"cb".into()).unwrap();
712 assert!(!stored_after);
713 }
714
715 #[wasm_bindgen_test]
716 #[allow(dead_code, clippy::unused_unit)]
717 fn bottom_button_callback_register_and_remove() {
718 let webapp = setup_webapp();
719 let main_button = Object::new();
720 let _ = Reflect::set(&webapp, &"MainButton".into(), &main_button);
721
722 let on_click = Function::new_with_args("cb", "this.cb = cb;");
723 let off_click = Function::new_with_args("", "delete this.cb;");
724 let _ = Reflect::set(&main_button, &"onClick".into(), &on_click);
725 let _ = Reflect::set(&main_button, &"offClick".into(), &off_click);
726
727 let called = Rc::new(Cell::new(false));
728 let called_clone = Rc::clone(&called);
729
730 let app = TelegramWebApp::instance().unwrap();
731 let handle = app
732 .set_bottom_button_callback(BottomButton::Main, move || {
733 called_clone.set(true);
734 })
735 .unwrap();
736
737 let stored = Reflect::has(&main_button, &"cb".into()).unwrap();
738 assert!(stored);
739
740 let cb_fn = Reflect::get(&main_button, &"cb".into())
741 .unwrap()
742 .dyn_into::<Function>()
743 .unwrap();
744 let _ = cb_fn.call0(&JsValue::NULL);
745 assert!(called.get());
746
747 app.remove_bottom_button_callback(handle).unwrap();
748 let stored_after = Reflect::has(&main_button, &"cb".into()).unwrap();
749 assert!(!stored_after);
750 }
751
752 #[wasm_bindgen_test]
753 #[allow(dead_code, clippy::unused_unit)]
754 fn secondary_button_callback_register_and_remove() {
755 let webapp = setup_webapp();
756 let secondary_button = Object::new();
757 let _ = Reflect::set(&webapp, &"SecondaryButton".into(), &secondary_button);
758
759 let on_click = Function::new_with_args("cb", "this.cb = cb;");
760 let off_click = Function::new_with_args("", "delete this.cb;");
761 let _ = Reflect::set(&secondary_button, &"onClick".into(), &on_click);
762 let _ = Reflect::set(&secondary_button, &"offClick".into(), &off_click);
763
764 let called = Rc::new(Cell::new(false));
765 let called_clone = Rc::clone(&called);
766
767 let app = TelegramWebApp::instance().unwrap();
768 let handle = app
769 .set_bottom_button_callback(BottomButton::Secondary, move || {
770 called_clone.set(true);
771 })
772 .unwrap();
773
774 let stored = Reflect::has(&secondary_button, &"cb".into()).unwrap();
775 assert!(stored);
776
777 let cb_fn = Reflect::get(&secondary_button, &"cb".into())
778 .unwrap()
779 .dyn_into::<Function>()
780 .unwrap();
781 let _ = cb_fn.call0(&JsValue::NULL);
782 assert!(called.get());
783
784 app.remove_bottom_button_callback(handle).unwrap();
785 let stored_after = Reflect::has(&secondary_button, &"cb".into()).unwrap();
786 assert!(!stored_after);
787 }
788
789 #[wasm_bindgen_test]
790 #[allow(dead_code, clippy::unused_unit)]
791 fn on_event_register_and_remove() {
792 let webapp = setup_webapp();
793 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
794 let off_event = Function::new_with_args("name", "delete this[name];");
795 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
796 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
797
798 let app = TelegramWebApp::instance().unwrap();
799 let handle = app.on_event("test", |_: JsValue| {}).unwrap();
800 assert!(Reflect::has(&webapp, &"test".into()).unwrap());
801 app.off_event(handle).unwrap();
802 assert!(!Reflect::has(&webapp, &"test".into()).unwrap());
803 }
804
805 #[wasm_bindgen_test]
806 #[allow(dead_code, clippy::unused_unit)]
807 fn background_event_register_and_remove() {
808 let webapp = setup_webapp();
809 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
810 let off_event = Function::new_with_args("name", "delete this[name];");
811 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
812 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
813
814 let app = TelegramWebApp::instance().unwrap();
815 let handle = app
816 .on_background_event(BackgroundEvent::MainButtonClicked, |_| {})
817 .unwrap();
818 assert!(Reflect::has(&webapp, &"mainButtonClicked".into()).unwrap());
819 app.off_event(handle).unwrap();
820 assert!(!Reflect::has(&webapp, &"mainButtonClicked".into()).unwrap());
821 }
822
823 #[wasm_bindgen_test]
824 #[allow(dead_code, clippy::unused_unit)]
825 fn background_event_delivers_data() {
826 let webapp = setup_webapp();
827 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
828 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
829
830 let app = TelegramWebApp::instance().unwrap();
831 let received = Rc::new(RefCell::new(String::new()));
832 let received_clone = Rc::clone(&received);
833 let _handle = app
834 .on_background_event(BackgroundEvent::InvoiceClosed, move |v| {
835 *received_clone.borrow_mut() = v.as_string().unwrap_or_default();
836 })
837 .unwrap();
838
839 let cb = Reflect::get(&webapp, &"invoiceClosed".into())
840 .unwrap()
841 .dyn_into::<Function>()
842 .unwrap();
843 let _ = cb.call1(&JsValue::NULL, &JsValue::from_str("paid"));
844 assert_eq!(received.borrow().as_str(), "paid");
845 }
846
847 #[wasm_bindgen_test]
848 #[allow(dead_code, clippy::unused_unit)]
849 fn theme_changed_register_and_remove() {
850 let webapp = setup_webapp();
851 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
852 let off_event = Function::new_with_args("name", "delete this[name];");
853 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
854 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
855
856 let app = TelegramWebApp::instance().unwrap();
857 let handle = app.on_theme_changed(|| {}).unwrap();
858 assert!(Reflect::has(&webapp, &"themeChanged".into()).unwrap());
859 app.off_event(handle).unwrap();
860 assert!(!Reflect::has(&webapp, &"themeChanged".into()).unwrap());
861 }
862
863 #[wasm_bindgen_test]
864 #[allow(dead_code, clippy::unused_unit)]
865 fn safe_area_changed_register_and_remove() {
866 let webapp = setup_webapp();
867 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
868 let off_event = Function::new_with_args("name", "delete this[name];");
869 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
870 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
871
872 let app = TelegramWebApp::instance().unwrap();
873 let handle = app.on_safe_area_changed(|| {}).unwrap();
874 assert!(Reflect::has(&webapp, &"safeAreaChanged".into()).unwrap());
875 app.off_event(handle).unwrap();
876 assert!(!Reflect::has(&webapp, &"safeAreaChanged".into()).unwrap());
877 }
878
879 #[wasm_bindgen_test]
880 #[allow(dead_code, clippy::unused_unit)]
881 fn content_safe_area_changed_register_and_remove() {
882 let webapp = setup_webapp();
883 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
884 let off_event = Function::new_with_args("name", "delete this[name];");
885 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
886 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
887
888 let app = TelegramWebApp::instance().unwrap();
889 let handle = app.on_content_safe_area_changed(|| {}).unwrap();
890 assert!(Reflect::has(&webapp, &"contentSafeAreaChanged".into()).unwrap());
891 app.off_event(handle).unwrap();
892 assert!(!Reflect::has(&webapp, &"contentSafeAreaChanged".into()).unwrap());
893 }
894
895 #[wasm_bindgen_test]
896 #[allow(dead_code, clippy::unused_unit)]
897 fn viewport_changed_register_and_remove() {
898 let webapp = setup_webapp();
899 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
900 let off_event = Function::new_with_args("name", "delete this[name];");
901 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
902 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
903
904 let app = TelegramWebApp::instance().unwrap();
905 let handle = app.on_viewport_changed(|| {}).unwrap();
906 assert!(Reflect::has(&webapp, &"viewportChanged".into()).unwrap());
907 app.off_event(handle).unwrap();
908 assert!(!Reflect::has(&webapp, &"viewportChanged".into()).unwrap());
909 }
910
911 #[wasm_bindgen_test]
912 #[allow(dead_code, clippy::unused_unit)]
913 fn clipboard_text_received_register_and_remove() {
914 let webapp = setup_webapp();
915 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
916 let off_event = Function::new_with_args("name", "delete this[name];");
917 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
918 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
919
920 let app = TelegramWebApp::instance().unwrap();
921 let handle = app.on_clipboard_text_received(|_| {}).unwrap();
922 assert!(Reflect::has(&webapp, &"clipboardTextReceived".into()).unwrap());
923 app.off_event(handle).unwrap();
924 assert!(!Reflect::has(&webapp, &"clipboardTextReceived".into()).unwrap());
925 }
926
927 #[wasm_bindgen_test]
928 #[allow(dead_code, clippy::unused_unit)]
929 fn open_link_and_telegram_link() {
930 let webapp = setup_webapp();
931 let open_link = Function::new_with_args("url", "this.open_link = url;");
932 let open_tg_link = Function::new_with_args("url", "this.open_tg_link = url;");
933 let _ = Reflect::set(&webapp, &"openLink".into(), &open_link);
934 let _ = Reflect::set(&webapp, &"openTelegramLink".into(), &open_tg_link);
935
936 let app = TelegramWebApp::instance().unwrap();
937 let url = "https://example.com";
938 app.open_link(url, None).unwrap();
939 app.open_telegram_link(url).unwrap();
940
941 assert_eq!(
942 Reflect::get(&webapp, &"open_link".into())
943 .unwrap()
944 .as_string()
945 .as_deref(),
946 Some(url)
947 );
948 assert_eq!(
949 Reflect::get(&webapp, &"open_tg_link".into())
950 .unwrap()
951 .as_string()
952 .as_deref(),
953 Some(url)
954 );
955 }
956
957 #[wasm_bindgen_test]
958 #[allow(dead_code, clippy::unused_unit)]
959 fn invoice_closed_register_and_remove() {
960 let webapp = setup_webapp();
961 let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
962 let off_event = Function::new_with_args("name", "delete this[name];");
963 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
964 let _ = Reflect::set(&webapp, &"offEvent".into(), &off_event);
965
966 let app = TelegramWebApp::instance().unwrap();
967 let handle = app.on_invoice_closed(|_| {}).unwrap();
968 assert!(Reflect::has(&webapp, &"invoiceClosed".into()).unwrap());
969 app.off_event(handle).unwrap();
970 assert!(!Reflect::has(&webapp, &"invoiceClosed".into()).unwrap());
971 }
972
973 #[wasm_bindgen_test]
974 #[allow(dead_code, clippy::unused_unit)]
975 fn invoice_closed_invokes_callback() {
976 let webapp = setup_webapp();
977 let on_event = Function::new_with_args("name, cb", "this.cb = cb;");
978 let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
979
980 let app = TelegramWebApp::instance().unwrap();
981 let status = Rc::new(RefCell::new(String::new()));
982 let status_clone = Rc::clone(&status);
983 app.on_invoice_closed(move |s| {
984 *status_clone.borrow_mut() = s;
985 })
986 .unwrap();
987
988 let cb = Reflect::get(&webapp, &"cb".into())
989 .unwrap()
990 .dyn_into::<Function>()
991 .unwrap();
992 cb.call1(&webapp, &"paid".into()).unwrap();
993 assert_eq!(status.borrow().as_str(), "paid");
994 cb.call1(&webapp, &"failed".into()).unwrap();
995 assert_eq!(status.borrow().as_str(), "failed");
996 }
997
998 #[wasm_bindgen_test]
999 #[allow(dead_code, clippy::unused_unit)]
1000 fn open_invoice_invokes_callback() {
1001 let webapp = setup_webapp();
1002 let open_invoice = Function::new_with_args("url, cb", "cb('paid');");
1003 let _ = Reflect::set(&webapp, &"openInvoice".into(), &open_invoice);
1004
1005 let app = TelegramWebApp::instance().unwrap();
1006 let status = Rc::new(RefCell::new(String::new()));
1007 let status_clone = Rc::clone(&status);
1008
1009 app.open_invoice("https://invoice", move |s| {
1010 *status_clone.borrow_mut() = s;
1011 })
1012 .unwrap();
1013
1014 assert_eq!(status.borrow().as_str(), "paid");
1015 }
1016
1017 #[wasm_bindgen_test]
1018 #[allow(dead_code, clippy::unused_unit)]
1019 fn switch_inline_query_calls_js() {
1020 let webapp = setup_webapp();
1021 let switch_inline =
1022 Function::new_with_args("query, types", "this.query = query; this.types = types;");
1023 let _ = Reflect::set(&webapp, &"switchInlineQuery".into(), &switch_inline);
1024
1025 let app = TelegramWebApp::instance().unwrap();
1026 let types = JsValue::from_str("users");
1027 app.switch_inline_query("search", Some(&types)).unwrap();
1028
1029 assert_eq!(
1030 Reflect::get(&webapp, &"query".into())
1031 .unwrap()
1032 .as_string()
1033 .as_deref(),
1034 Some("search"),
1035 );
1036 assert_eq!(
1037 Reflect::get(&webapp, &"types".into())
1038 .unwrap()
1039 .as_string()
1040 .as_deref(),
1041 Some("users"),
1042 );
1043 }
1044
1045 #[wasm_bindgen_test]
1046 #[allow(dead_code, clippy::unused_unit)]
1047 fn share_message_calls_js() {
1048 let webapp = setup_webapp();
1049 let share = Function::new_with_args("id, cb", "this.shared_id = id; cb(true);");
1050 let _ = Reflect::set(&webapp, &"shareMessage".into(), &share);
1051
1052 let app = TelegramWebApp::instance().unwrap();
1053 let sent = Rc::new(Cell::new(false));
1054 let sent_clone = Rc::clone(&sent);
1055
1056 app.share_message("123", move |s| {
1057 sent_clone.set(s);
1058 })
1059 .unwrap();
1060
1061 assert_eq!(
1062 Reflect::get(&webapp, &"shared_id".into())
1063 .unwrap()
1064 .as_string()
1065 .as_deref(),
1066 Some("123"),
1067 );
1068 assert!(sent.get());
1069 }
1070
1071 #[wasm_bindgen_test]
1072 #[allow(dead_code, clippy::unused_unit)]
1073 fn share_to_story_calls_js() {
1074 let webapp = setup_webapp();
1075 let share = Function::new_with_args(
1076 "url, params",
1077 "this.story_url = url; this.story_params = params;"
1078 );
1079 let _ = Reflect::set(&webapp, &"shareToStory".into(), &share);
1080
1081 let app = TelegramWebApp::instance().unwrap();
1082 let url = "https://example.com/media";
1083 let params = Object::new();
1084 let _ = Reflect::set(¶ms, &"text".into(), &"hi".into());
1085 app.share_to_story(url, Some(¶ms.into())).unwrap();
1086
1087 assert_eq!(
1088 Reflect::get(&webapp, &"story_url".into())
1089 .unwrap()
1090 .as_string()
1091 .as_deref(),
1092 Some(url),
1093 );
1094 let stored = Reflect::get(&webapp, &"story_params".into()).unwrap();
1095 assert_eq!(
1096 Reflect::get(&stored, &"text".into())
1097 .unwrap()
1098 .as_string()
1099 .as_deref(),
1100 Some("hi"),
1101 );
1102 }
1103
1104 #[wasm_bindgen_test]
1105 #[allow(dead_code, clippy::unused_unit)]
1106 fn share_url_calls_js() {
1107 let webapp = setup_webapp();
1108 let share = Function::new_with_args(
1109 "url, text",
1110 "this.shared_url = url; this.shared_text = text;"
1111 );
1112 let _ = Reflect::set(&webapp, &"shareURL".into(), &share);
1113
1114 let app = TelegramWebApp::instance().unwrap();
1115 let url = "https://example.com";
1116 let text = "check";
1117 app.share_url(url, Some(text)).unwrap();
1118
1119 assert_eq!(
1120 Reflect::get(&webapp, &"shared_url".into())
1121 .unwrap()
1122 .as_string()
1123 .as_deref(),
1124 Some(url),
1125 );
1126 assert_eq!(
1127 Reflect::get(&webapp, &"shared_text".into())
1128 .unwrap()
1129 .as_string()
1130 .as_deref(),
1131 Some(text),
1132 );
1133 }
1134
1135 #[wasm_bindgen_test]
1136 #[allow(dead_code, clippy::unused_unit)]
1137 fn join_voice_chat_calls_js() {
1138 let webapp = setup_webapp();
1139 let join = Function::new_with_args(
1140 "id, hash",
1141 "this.voice_chat_id = id; this.voice_chat_hash = hash;"
1142 );
1143 let _ = Reflect::set(&webapp, &"joinVoiceChat".into(), &join);
1144
1145 let app = TelegramWebApp::instance().unwrap();
1146 app.join_voice_chat("123", Some("hash")).unwrap();
1147
1148 assert_eq!(
1149 Reflect::get(&webapp, &"voice_chat_id".into())
1150 .unwrap()
1151 .as_string()
1152 .as_deref(),
1153 Some("123"),
1154 );
1155 assert_eq!(
1156 Reflect::get(&webapp, &"voice_chat_hash".into())
1157 .unwrap()
1158 .as_string()
1159 .as_deref(),
1160 Some("hash"),
1161 );
1162 }
1163
1164 #[wasm_bindgen_test]
1165 #[allow(dead_code, clippy::unused_unit)]
1166 fn add_to_home_screen_calls_js() {
1167 let webapp = setup_webapp();
1168 let add = Function::new_with_args("", "this.called = true; return true;");
1169 let _ = Reflect::set(&webapp, &"addToHomeScreen".into(), &add);
1170
1171 let app = TelegramWebApp::instance().unwrap();
1172 let shown = app.add_to_home_screen().unwrap();
1173 assert!(shown);
1174 let called = Reflect::get(&webapp, &"called".into())
1175 .unwrap()
1176 .as_bool()
1177 .unwrap_or(false);
1178 assert!(called);
1179 }
1180
1181 #[wasm_bindgen_test]
1182 #[allow(dead_code, clippy::unused_unit)]
1183 fn request_fullscreen_calls_js() {
1184 let webapp = setup_webapp();
1185 let called = Rc::new(Cell::new(false));
1186 let called_clone = Rc::clone(&called);
1187
1188 let cb = Closure::<dyn FnMut()>::new(move || {
1189 called_clone.set(true);
1190 });
1191 let _ = Reflect::set(
1192 &webapp,
1193 &"requestFullscreen".into(),
1194 cb.as_ref().unchecked_ref()
1195 );
1196 cb.forget();
1197
1198 let app = TelegramWebApp::instance().unwrap();
1199 app.request_fullscreen().unwrap();
1200 assert!(called.get());
1201 }
1202
1203 #[wasm_bindgen_test]
1204 #[allow(dead_code, clippy::unused_unit)]
1205 fn exit_fullscreen_calls_js() {
1206 let webapp = setup_webapp();
1207 let called = Rc::new(Cell::new(false));
1208 let called_clone = Rc::clone(&called);
1209
1210 let cb = Closure::<dyn FnMut()>::new(move || {
1211 called_clone.set(true);
1212 });
1213 let _ = Reflect::set(
1214 &webapp,
1215 &"exitFullscreen".into(),
1216 cb.as_ref().unchecked_ref()
1217 );
1218 cb.forget();
1219
1220 let app = TelegramWebApp::instance().unwrap();
1221 app.exit_fullscreen().unwrap();
1222 assert!(called.get());
1223 }
1224
1225 #[wasm_bindgen_test]
1226 #[allow(dead_code, clippy::unused_unit)]
1227 fn check_home_screen_status_invokes_callback() {
1228 let webapp = setup_webapp();
1229 let check = Function::new_with_args("cb", "cb('added');");
1230 let _ = Reflect::set(&webapp, &"checkHomeScreenStatus".into(), &check);
1231
1232 let app = TelegramWebApp::instance().unwrap();
1233 let status = Rc::new(RefCell::new(String::new()));
1234 let status_clone = Rc::clone(&status);
1235
1236 app.check_home_screen_status(move |s| {
1237 *status_clone.borrow_mut() = s;
1238 })
1239 .unwrap();
1240
1241 assert_eq!(status.borrow().as_str(), "added");
1242 }
1243
1244 #[wasm_bindgen_test]
1245 #[allow(dead_code, clippy::unused_unit)]
1246 fn lock_orientation_calls_js() {
1247 let webapp = setup_webapp();
1248 let received = Rc::new(RefCell::new(None));
1249 let rc_clone = Rc::clone(&received);
1250
1251 let cb = Closure::<dyn FnMut(JsValue)>::new(move |v: JsValue| {
1252 *rc_clone.borrow_mut() = v.as_string();
1253 });
1254 let _ = Reflect::set(
1255 &webapp,
1256 &"lockOrientation".into(),
1257 cb.as_ref().unchecked_ref()
1258 );
1259 cb.forget();
1260
1261 let app = TelegramWebApp::instance().unwrap();
1262 app.lock_orientation("portrait").unwrap();
1263 assert_eq!(received.borrow().as_deref(), Some("portrait"));
1264 }
1265
1266 #[wasm_bindgen_test]
1267 #[allow(dead_code, clippy::unused_unit)]
1268 fn unlock_orientation_calls_js() {
1269 let webapp = setup_webapp();
1270 let called = Rc::new(Cell::new(false));
1271 let called_clone = Rc::clone(&called);
1272
1273 let cb = Closure::<dyn FnMut()>::new(move || {
1274 called_clone.set(true);
1275 });
1276 let _ = Reflect::set(
1277 &webapp,
1278 &"unlockOrientation".into(),
1279 cb.as_ref().unchecked_ref()
1280 );
1281 cb.forget();
1282
1283 let app = TelegramWebApp::instance().unwrap();
1284 app.unlock_orientation().unwrap();
1285 assert!(called.get());
1286 }
1287
1288 #[wasm_bindgen_test]
1289 #[allow(dead_code, clippy::unused_unit)]
1290 fn enable_vertical_swipes_calls_js() {
1291 let webapp = setup_webapp();
1292 let called = Rc::new(Cell::new(false));
1293 let called_clone = Rc::clone(&called);
1294
1295 let cb = Closure::<dyn FnMut()>::new(move || {
1296 called_clone.set(true);
1297 });
1298 let _ = Reflect::set(
1299 &webapp,
1300 &"enableVerticalSwipes".into(),
1301 cb.as_ref().unchecked_ref()
1302 );
1303 cb.forget();
1304
1305 let app = TelegramWebApp::instance().unwrap();
1306 app.enable_vertical_swipes().unwrap();
1307 assert!(called.get());
1308 }
1309
1310 #[wasm_bindgen_test]
1311 #[allow(dead_code, clippy::unused_unit)]
1312 fn disable_vertical_swipes_calls_js() {
1313 let webapp = setup_webapp();
1314 let called = Rc::new(Cell::new(false));
1315 let called_clone = Rc::clone(&called);
1316
1317 let cb = Closure::<dyn FnMut()>::new(move || {
1318 called_clone.set(true);
1319 });
1320 let _ = Reflect::set(
1321 &webapp,
1322 &"disableVerticalSwipes".into(),
1323 cb.as_ref().unchecked_ref()
1324 );
1325 cb.forget();
1326
1327 let app = TelegramWebApp::instance().unwrap();
1328 app.disable_vertical_swipes().unwrap();
1329 assert!(called.get());
1330 }
1331
1332 #[wasm_bindgen_test]
1333 #[allow(dead_code, clippy::unused_unit)]
1334 fn request_write_access_invokes_callback() {
1335 let webapp = setup_webapp();
1336 let request = Function::new_with_args("cb", "cb(true);");
1337 let _ = Reflect::set(&webapp, &"requestWriteAccess".into(), &request);
1338
1339 let app = TelegramWebApp::instance().unwrap();
1340 let granted = Rc::new(Cell::new(false));
1341 let granted_clone = Rc::clone(&granted);
1342
1343 let res = app.request_write_access(move |g| {
1344 granted_clone.set(g);
1345 });
1346 assert!(res.is_ok());
1347
1348 assert!(granted.get());
1349 }
1350
1351 #[wasm_bindgen_test]
1352 #[allow(dead_code, clippy::unused_unit)]
1353 fn download_file_invokes_callback() {
1354 let webapp = setup_webapp();
1355 let received_url = Rc::new(RefCell::new(String::new()));
1356 let received_name = Rc::new(RefCell::new(String::new()));
1357 let url_clone = Rc::clone(&received_url);
1358 let name_clone = Rc::clone(&received_name);
1359
1360 let download = Closure::<dyn FnMut(JsValue, JsValue)>::new(move |params, cb: JsValue| {
1361 let url = Reflect::get(¶ms, &"url".into())
1362 .unwrap()
1363 .as_string()
1364 .unwrap_or_default();
1365 let name = Reflect::get(¶ms, &"file_name".into())
1366 .unwrap()
1367 .as_string()
1368 .unwrap_or_default();
1369 *url_clone.borrow_mut() = url;
1370 *name_clone.borrow_mut() = name;
1371 let func = cb.dyn_ref::<Function>().unwrap();
1372 let _ = func.call1(&JsValue::NULL, &JsValue::from_str("id"));
1373 });
1374 let _ = Reflect::set(
1375 &webapp,
1376 &"downloadFile".into(),
1377 download.as_ref().unchecked_ref()
1378 );
1379 download.forget();
1380
1381 let app = TelegramWebApp::instance().unwrap();
1382 let result = Rc::new(RefCell::new(String::new()));
1383 let result_clone = Rc::clone(&result);
1384 let params = DownloadFileParams {
1385 url: "https://example.com/data.bin",
1386 file_name: Some("data.bin"),
1387 mime_type: None
1388 };
1389 app.download_file(params, move |id| {
1390 *result_clone.borrow_mut() = id;
1391 })
1392 .unwrap();
1393
1394 assert_eq!(
1395 received_url.borrow().as_str(),
1396 "https://example.com/data.bin"
1397 );
1398 assert_eq!(received_name.borrow().as_str(), "data.bin");
1399 assert_eq!(result.borrow().as_str(), "id");
1400 }
1401
1402 #[wasm_bindgen_test]
1403 #[allow(dead_code, clippy::unused_unit)]
1404 fn request_write_access_returns_error_when_missing() {
1405 let _webapp = setup_webapp();
1406 let app = TelegramWebApp::instance().unwrap();
1407 let res = app.request_write_access(|_| {});
1408 assert!(res.is_err());
1409 }
1410 #[wasm_bindgen_test]
1411 #[allow(dead_code, clippy::unused_unit)]
1412 fn request_emoji_status_access_invokes_callback() {
1413 let webapp = setup_webapp();
1414 let request = Function::new_with_args("cb", "cb(false);");
1415 let _ = Reflect::set(&webapp, &"requestEmojiStatusAccess".into(), &request);
1416
1417 let app = TelegramWebApp::instance().unwrap();
1418 let granted = Rc::new(Cell::new(true));
1419 let granted_clone = Rc::clone(&granted);
1420
1421 app.request_emoji_status_access(move |g| {
1422 granted_clone.set(g);
1423 })
1424 .unwrap();
1425
1426 assert!(!granted.get());
1427 }
1428
1429 #[wasm_bindgen_test]
1430 #[allow(dead_code, clippy::unused_unit)]
1431 fn set_emoji_status_invokes_callback() {
1432 let webapp = setup_webapp();
1433 let set_status = Function::new_with_args("status, cb", "this.st = status; cb(true);");
1434 let _ = Reflect::set(&webapp, &"setEmojiStatus".into(), &set_status);
1435
1436 let status = Object::new();
1437 let _ = Reflect::set(
1438 &status,
1439 &"custom_emoji_id".into(),
1440 &JsValue::from_str("321")
1441 );
1442
1443 let app = TelegramWebApp::instance().unwrap();
1444 let success = Rc::new(Cell::new(false));
1445 let success_clone = Rc::clone(&success);
1446
1447 app.set_emoji_status(&status.into(), move |s| {
1448 success_clone.set(s);
1449 })
1450 .unwrap();
1451
1452 assert!(success.get());
1453 let stored = Reflect::get(&webapp, &"st".into()).unwrap();
1454 let id = Reflect::get(&stored, &"custom_emoji_id".into())
1455 .unwrap()
1456 .as_string();
1457 assert_eq!(id.as_deref(), Some("321"));
1458 }
1459
1460 #[wasm_bindgen_test]
1461 #[allow(dead_code, clippy::unused_unit)]
1462 fn show_popup_invokes_callback() {
1463 let webapp = setup_webapp();
1464 let show_popup = Function::new_with_args("params, cb", "cb('ok');");
1465 let _ = Reflect::set(&webapp, &"showPopup".into(), &show_popup);
1466
1467 let app = TelegramWebApp::instance().unwrap();
1468 let button = Rc::new(RefCell::new(String::new()));
1469 let button_clone = Rc::clone(&button);
1470
1471 app.show_popup(&JsValue::NULL, move |id| {
1472 *button_clone.borrow_mut() = id;
1473 })
1474 .unwrap();
1475
1476 assert_eq!(button.borrow().as_str(), "ok");
1477 }
1478
1479 #[wasm_bindgen_test]
1480 #[allow(dead_code, clippy::unused_unit)]
1481 fn read_text_from_clipboard_invokes_callback() {
1482 let webapp = setup_webapp();
1483 let read_clip = Function::new_with_args("cb", "cb('clip');");
1484 let _ = Reflect::set(&webapp, &"readTextFromClipboard".into(), &read_clip);
1485
1486 let app = TelegramWebApp::instance().unwrap();
1487 let text = Rc::new(RefCell::new(String::new()));
1488 let text_clone = Rc::clone(&text);
1489
1490 app.read_text_from_clipboard(move |t| {
1491 *text_clone.borrow_mut() = t;
1492 })
1493 .unwrap();
1494
1495 assert_eq!(text.borrow().as_str(), "clip");
1496 }
1497
1498 #[wasm_bindgen_test]
1499 #[allow(dead_code, clippy::unused_unit)]
1500 fn scan_qr_popup_invokes_callback_and_close() {
1501 let webapp = setup_webapp();
1502 let show_scan = Function::new_with_args("text, cb", "cb('code');");
1503 let close_scan = Function::new_with_args("", "this.closed = true;");
1504 let _ = Reflect::set(&webapp, &"showScanQrPopup".into(), &show_scan);
1505 let _ = Reflect::set(&webapp, &"closeScanQrPopup".into(), &close_scan);
1506
1507 let app = TelegramWebApp::instance().unwrap();
1508 let text = Rc::new(RefCell::new(String::new()));
1509 let text_clone = Rc::clone(&text);
1510
1511 app.show_scan_qr_popup("scan", move |value| {
1512 *text_clone.borrow_mut() = value;
1513 })
1514 .unwrap();
1515 assert_eq!(text.borrow().as_str(), "code");
1516
1517 app.close_scan_qr_popup().unwrap();
1518 let closed = Reflect::get(&webapp, &"closed".into())
1519 .unwrap()
1520 .as_bool()
1521 .unwrap_or(false);
1522 assert!(closed);
1523 }
1524}