1use std::{collections::HashMap, fmt::Display};
2
3use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
4use tauri::plugin::PermissionState;
5
6use url::Url;
7
8#[derive(Debug, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct PermissionResponse {
11 pub permission_state: PermissionState,
12}
13
14#[cfg(feature = "push-notifications")]
15#[derive(Debug, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct PushNotificationResponse {
18 pub device_token: String,
19}
20
21#[derive(Debug, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct Attachment {
24 id: String,
25 url: Url,
26}
27
28impl Attachment {
29 pub fn new(id: impl Into<String>, url: Url) -> Self {
30 Self { id: id.into(), url }
31 }
32}
33
34#[derive(Debug, Default, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct ScheduleInterval {
37 pub year: Option<u8>,
38 pub month: Option<u8>,
39 pub day: Option<u8>,
40 pub weekday: Option<u8>,
41 pub hour: Option<u8>,
42 pub minute: Option<u8>,
43 pub second: Option<u8>,
44}
45
46#[derive(Debug)]
47pub enum ScheduleEvery {
48 Year,
49 Month,
50 TwoWeeks,
51 Week,
52 Day,
53 Hour,
54 Minute,
55 Second,
56}
57
58impl Display for ScheduleEvery {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 write!(
61 f,
62 "{}",
63 match self {
64 Self::Year => "year",
65 Self::Month => "month",
66 Self::TwoWeeks => "twoWeeks",
67 Self::Week => "week",
68 Self::Day => "day",
69 Self::Hour => "hour",
70 Self::Minute => "minute",
71 Self::Second => "second",
72 }
73 )
74 }
75}
76
77impl Serialize for ScheduleEvery {
78 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
79 where
80 S: Serializer,
81 {
82 serializer.serialize_str(self.to_string().as_ref())
83 }
84}
85
86impl<'de> Deserialize<'de> for ScheduleEvery {
87 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
88 where
89 D: Deserializer<'de>,
90 {
91 let s = String::deserialize(deserializer)?;
92 match s.to_lowercase().as_str() {
93 "year" => Ok(Self::Year),
94 "month" => Ok(Self::Month),
95 "twoweeks" => Ok(Self::TwoWeeks),
96 "week" => Ok(Self::Week),
97 "day" => Ok(Self::Day),
98 "hour" => Ok(Self::Hour),
99 "minute" => Ok(Self::Minute),
100 "second" => Ok(Self::Second),
101 _ => Err(DeError::custom(format!("unknown every kind '{s}'"))),
102 }
103 }
104}
105
106#[derive(Debug, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub enum Schedule {
109 #[serde(rename_all = "camelCase")]
110 At {
111 #[serde(
112 serialize_with = "iso8601::serialize",
113 deserialize_with = "time::serde::iso8601::deserialize"
114 )]
115 date: time::OffsetDateTime,
116 #[serde(default)]
117 repeating: bool,
118 #[serde(default)]
119 allow_while_idle: bool,
120 },
121 #[serde(rename_all = "camelCase")]
122 Interval {
123 interval: ScheduleInterval,
124 #[serde(default)]
125 allow_while_idle: bool,
126 },
127 #[serde(rename_all = "camelCase")]
128 Every {
129 interval: ScheduleEvery,
130 count: u8,
131 #[serde(default)]
132 allow_while_idle: bool,
133 },
134}
135
136mod iso8601 {
138 use serde::{ser::Error as _, Serialize, Serializer};
139 use time::{
140 format_description::well_known::iso8601::{Config, EncodedConfig},
141 format_description::well_known::Iso8601,
142 OffsetDateTime,
143 };
144
145 const SERDE_CONFIG: EncodedConfig = Config::DEFAULT.encode();
146
147 pub fn serialize<S: Serializer>(
148 datetime: &OffsetDateTime,
149 serializer: S,
150 ) -> Result<S::Ok, S::Error> {
151 datetime
152 .format(&Iso8601::<SERDE_CONFIG>)
153 .map_err(S::Error::custom)?
154 .serialize(serializer)
155 }
156}
157
158#[allow(clippy::struct_excessive_bools)]
160#[derive(Debug, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct NotificationData {
163 #[serde(default = "default_id")]
164 pub(crate) id: i32,
165 pub(crate) channel_id: Option<String>,
166 pub(crate) title: Option<String>,
167 pub(crate) body: Option<String>,
168 pub(crate) schedule: Option<Schedule>,
169 pub(crate) large_body: Option<String>,
170 pub(crate) summary: Option<String>,
171 pub(crate) action_type_id: Option<String>,
172 pub(crate) group: Option<String>,
173 #[serde(default)]
174 pub(crate) group_summary: bool,
175 pub(crate) sound: Option<String>,
176 #[serde(default)]
177 pub(crate) inbox_lines: Vec<String>,
178 pub(crate) icon: Option<String>,
179 pub(crate) large_icon: Option<String>,
180 pub(crate) icon_color: Option<String>,
181 #[serde(default)]
182 pub(crate) attachments: Vec<Attachment>,
183 #[serde(default)]
184 pub(crate) extra: HashMap<String, serde_json::Value>,
185 #[serde(default)]
186 pub(crate) ongoing: bool,
187 #[serde(default)]
188 pub(crate) auto_cancel: bool,
189 #[serde(default)]
190 pub(crate) silent: bool,
191}
192
193fn default_id() -> i32 {
194 rand::random()
195}
196
197impl Default for NotificationData {
198 fn default() -> Self {
199 Self {
200 id: default_id(),
201 channel_id: None,
202 title: None,
203 body: None,
204 schedule: None,
205 large_body: None,
206 summary: None,
207 action_type_id: None,
208 group: None,
209 group_summary: false,
210 sound: None,
211 inbox_lines: Vec::new(),
212 icon: None,
213 large_icon: None,
214 icon_color: None,
215 attachments: Vec::new(),
216 extra: HashMap::default(),
217 ongoing: false,
218 auto_cancel: false,
219 silent: false,
220 }
221 }
222}
223
224#[derive(Debug, Deserialize, Serialize)]
225#[serde(rename_all = "camelCase")]
226pub struct PendingNotification {
227 id: i32,
228 title: Option<String>,
229 body: Option<String>,
230 schedule: Schedule,
231}
232
233impl PendingNotification {
234 #[must_use]
235 pub const fn id(&self) -> i32 {
236 self.id
237 }
238
239 #[must_use]
240 pub fn title(&self) -> Option<&str> {
241 self.title.as_deref()
242 }
243
244 #[must_use]
245 pub fn body(&self) -> Option<&str> {
246 self.body.as_deref()
247 }
248
249 #[must_use]
250 pub const fn schedule(&self) -> &Schedule {
251 &self.schedule
252 }
253}
254
255#[derive(Debug, Deserialize, Serialize)]
256#[serde(rename_all = "camelCase")]
257pub struct ActiveNotification {
258 id: i32,
259 tag: Option<String>,
260 title: Option<String>,
261 body: Option<String>,
262 group: Option<String>,
263 #[serde(default)]
264 group_summary: bool,
265 #[serde(default)]
266 data: HashMap<String, String>,
267 #[serde(default)]
268 extra: HashMap<String, serde_json::Value>,
269 #[serde(default)]
270 attachments: Vec<Attachment>,
271 action_type_id: Option<String>,
272 schedule: Option<Schedule>,
273 sound: Option<String>,
274}
275
276impl ActiveNotification {
277 #[must_use]
278 pub const fn id(&self) -> i32 {
279 self.id
280 }
281
282 #[must_use]
283 pub fn tag(&self) -> Option<&str> {
284 self.tag.as_deref()
285 }
286
287 #[must_use]
288 pub fn title(&self) -> Option<&str> {
289 self.title.as_deref()
290 }
291
292 #[must_use]
293 pub fn body(&self) -> Option<&str> {
294 self.body.as_deref()
295 }
296
297 #[must_use]
298 pub fn group(&self) -> Option<&str> {
299 self.group.as_deref()
300 }
301
302 #[must_use]
303 pub const fn group_summary(&self) -> bool {
304 self.group_summary
305 }
306
307 #[must_use]
308 pub const fn data(&self) -> &HashMap<String, String> {
309 &self.data
310 }
311
312 #[must_use]
313 pub const fn extra(&self) -> &HashMap<String, serde_json::Value> {
314 &self.extra
315 }
316
317 #[must_use]
318 pub fn attachments(&self) -> &[Attachment] {
319 &self.attachments
320 }
321
322 #[must_use]
323 pub fn action_type_id(&self) -> Option<&str> {
324 self.action_type_id.as_deref()
325 }
326
327 #[must_use]
328 pub const fn schedule(&self) -> Option<&Schedule> {
329 self.schedule.as_ref()
330 }
331
332 #[must_use]
333 pub fn sound(&self) -> Option<&str> {
334 self.sound.as_deref()
335 }
336}
337
338#[allow(clippy::struct_excessive_bools)]
340#[derive(Debug, Serialize, Deserialize)]
341#[serde(rename_all = "camelCase")]
342pub struct ActionType {
343 id: String,
344 actions: Vec<Action>,
345 hidden_previews_body_placeholder: Option<String>,
346 #[serde(default)]
347 custom_dismiss_action: bool,
348 #[serde(default)]
349 allow_in_car_play: bool,
350 #[serde(default)]
351 hidden_previews_show_title: bool,
352 #[serde(default)]
353 hidden_previews_show_subtitle: bool,
354}
355
356#[allow(clippy::struct_excessive_bools)]
358#[derive(Debug, Serialize, Deserialize)]
359#[serde(rename_all = "camelCase")]
360pub struct Action {
361 id: String,
362 title: String,
363 #[serde(default)]
364 requires_authentication: bool,
365 #[serde(default)]
366 foreground: bool,
367 #[serde(default)]
368 destructive: bool,
369 #[serde(default)]
370 input: bool,
371 input_button_title: Option<String>,
372 input_placeholder: Option<String>,
373}
374
375pub use android::*;
376
377mod android {
378 use serde::{Deserialize, Serialize};
379 use serde_repr::{Deserialize_repr, Serialize_repr};
380
381 #[derive(Debug, Default, Clone, Copy, Serialize_repr, Deserialize_repr)]
382 #[repr(u8)]
383 pub enum Importance {
384 None = 0,
385 Min = 1,
386 Low = 2,
387 #[default]
388 Default = 3,
389 High = 4,
390 }
391
392 #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
393 #[repr(i8)]
394 pub enum Visibility {
395 Secret = -1,
396 Private = 0,
397 Public = 1,
398 }
399
400 #[derive(Debug, Serialize, Deserialize)]
401 #[serde(rename_all = "camelCase")]
402 pub struct Channel {
403 id: String,
404 name: String,
405 description: Option<String>,
406 sound: Option<String>,
407 lights: Option<bool>,
408 light_color: Option<String>,
409 vibration: Option<bool>,
410 importance: Option<Importance>,
411 visibility: Option<Visibility>,
412 }
413
414 #[derive(Debug)]
415 pub struct ChannelBuilder(Channel);
416
417 impl Channel {
418 pub fn builder(id: impl Into<String>, name: impl Into<String>) -> ChannelBuilder {
419 ChannelBuilder(Self {
420 id: id.into(),
421 name: name.into(),
422 description: None,
423 sound: None,
424 lights: Some(false),
425 light_color: None,
426 vibration: Some(false),
427 importance: None,
428 visibility: None,
429 })
430 }
431
432 #[must_use]
433 pub fn id(&self) -> &str {
434 &self.id
435 }
436
437 #[must_use]
438 pub fn name(&self) -> &str {
439 &self.name
440 }
441
442 #[must_use]
443 pub fn description(&self) -> Option<&str> {
444 self.description.as_deref()
445 }
446
447 #[must_use]
448 pub fn sound(&self) -> Option<&str> {
449 self.sound.as_deref()
450 }
451
452 #[must_use]
453 pub fn lights(&self) -> bool {
454 self.lights.unwrap_or(false)
455 }
456
457 #[must_use]
458 pub fn light_color(&self) -> Option<&str> {
459 self.light_color.as_deref()
460 }
461
462 #[must_use]
463 pub fn vibration(&self) -> bool {
464 self.vibration.unwrap_or(false)
465 }
466
467 #[must_use]
468 pub fn importance(&self) -> Importance {
469 self.importance.unwrap_or_default()
470 }
471
472 #[must_use]
473 pub const fn visibility(&self) -> Option<Visibility> {
474 self.visibility
475 }
476 }
477
478 impl ChannelBuilder {
479 #[must_use]
480 pub fn description(mut self, description: impl Into<String>) -> Self {
481 self.0.description.replace(description.into());
482 self
483 }
484
485 #[must_use]
486 pub fn sound(mut self, sound: impl Into<String>) -> Self {
487 self.0.sound.replace(sound.into());
488 self
489 }
490
491 #[must_use]
492 pub const fn lights(mut self, lights: bool) -> Self {
493 self.0.lights = Some(lights);
494 self
495 }
496
497 #[must_use]
498 pub fn light_color(mut self, color: impl Into<String>) -> Self {
499 self.0.light_color.replace(color.into());
500 self
501 }
502
503 #[must_use]
504 pub const fn vibration(mut self, vibration: bool) -> Self {
505 self.0.vibration = Some(vibration);
506 self
507 }
508
509 #[must_use]
510 pub const fn importance(mut self, importance: Importance) -> Self {
511 self.0.importance = Some(importance);
512 self
513 }
514
515 #[must_use]
516 pub fn visibility(mut self, visibility: Visibility) -> Self {
517 self.0.visibility.replace(visibility);
518 self
519 }
520
521 #[must_use]
522 pub fn build(self) -> Channel {
523 self.0
524 }
525 }
526}
527
528#[cfg(test)]
529mod tests {
530 use super::*;
531
532 #[test]
533 fn test_attachment_creation() {
534 let url = Url::parse("https://example.com/image.png").expect("Failed to parse URL");
535 let attachment = Attachment::new("test_id", url.clone());
536 assert_eq!(attachment.id, "test_id");
537 assert_eq!(attachment.url, url);
538 }
539
540 #[test]
541 fn test_attachment_serialization() {
542 let url = Url::parse("https://example.com/image.png").expect("Failed to parse URL");
543 let attachment = Attachment::new("test_id", url);
544 let json = serde_json::to_string(&attachment).expect("Failed to serialize attachment");
545 assert!(json.contains("test_id"));
546 assert!(json.contains("https://example.com/image.png"));
547 }
548
549 #[test]
550 fn test_attachment_deserialization() {
551 let json = r#"{"id":"test_id","url":"https://example.com/image.png"}"#;
552 let attachment: Attachment =
553 serde_json::from_str(json).expect("Failed to deserialize attachment");
554 assert_eq!(attachment.id, "test_id");
555 assert_eq!(attachment.url.as_str(), "https://example.com/image.png");
556 }
557
558 #[test]
559 fn test_schedule_every_display() {
560 assert_eq!(ScheduleEvery::Year.to_string(), "year");
561 assert_eq!(ScheduleEvery::Month.to_string(), "month");
562 assert_eq!(ScheduleEvery::TwoWeeks.to_string(), "twoWeeks");
563 assert_eq!(ScheduleEvery::Week.to_string(), "week");
564 assert_eq!(ScheduleEvery::Day.to_string(), "day");
565 assert_eq!(ScheduleEvery::Hour.to_string(), "hour");
566 assert_eq!(ScheduleEvery::Minute.to_string(), "minute");
567 assert_eq!(ScheduleEvery::Second.to_string(), "second");
568 }
569
570 #[test]
571 fn test_schedule_every_serialization() {
572 let json = serde_json::to_string(&ScheduleEvery::Day).expect("Failed to serialize Day");
573 assert_eq!(json, "\"day\"");
574
575 let json =
576 serde_json::to_string(&ScheduleEvery::TwoWeeks).expect("Failed to serialize TwoWeeks");
577 assert_eq!(json, "\"twoWeeks\"");
578 }
579
580 #[test]
581 fn test_schedule_every_deserialization() {
582 let every: ScheduleEvery =
583 serde_json::from_str("\"year\"").expect("Failed to deserialize year");
584 assert!(matches!(every, ScheduleEvery::Year));
585
586 let every: ScheduleEvery =
587 serde_json::from_str("\"month\"").expect("Failed to deserialize month");
588 assert!(matches!(every, ScheduleEvery::Month));
589
590 let every: ScheduleEvery =
591 serde_json::from_str("\"twoweeks\"").expect("Failed to deserialize twoweeks");
592 assert!(matches!(every, ScheduleEvery::TwoWeeks));
593
594 let every: ScheduleEvery =
595 serde_json::from_str("\"week\"").expect("Failed to deserialize week");
596 assert!(matches!(every, ScheduleEvery::Week));
597
598 let every: ScheduleEvery =
599 serde_json::from_str("\"day\"").expect("Failed to deserialize day");
600 assert!(matches!(every, ScheduleEvery::Day));
601
602 let every: ScheduleEvery =
603 serde_json::from_str("\"hour\"").expect("Failed to deserialize hour");
604 assert!(matches!(every, ScheduleEvery::Hour));
605
606 let every: ScheduleEvery =
607 serde_json::from_str("\"minute\"").expect("Failed to deserialize minute");
608 assert!(matches!(every, ScheduleEvery::Minute));
609
610 let every: ScheduleEvery =
611 serde_json::from_str("\"second\"").expect("Failed to deserialize second");
612 assert!(matches!(every, ScheduleEvery::Second));
613 }
614
615 #[test]
616 fn test_schedule_every_deserialization_invalid() {
617 let result: Result<ScheduleEvery, _> = serde_json::from_str("\"invalid\"");
618 assert!(result.is_err());
619 }
620
621 #[test]
622 fn test_schedule_interval_default() {
623 let interval = ScheduleInterval::default();
624 assert!(interval.year.is_none());
625 assert!(interval.month.is_none());
626 assert!(interval.day.is_none());
627 assert!(interval.weekday.is_none());
628 assert!(interval.hour.is_none());
629 assert!(interval.minute.is_none());
630 assert!(interval.second.is_none());
631 }
632
633 #[test]
634 fn test_schedule_interval_serialization() {
635 let interval = ScheduleInterval {
636 year: Some(24),
637 month: Some(12),
638 day: Some(25),
639 weekday: Some(1),
640 hour: Some(10),
641 minute: Some(30),
642 second: Some(0),
643 };
644 let json = serde_json::to_string(&interval).expect("Failed to serialize interval");
645 assert!(json.contains("\"year\":24"));
646 assert!(json.contains("\"month\":12"));
647 assert!(json.contains("\"day\":25"));
648 }
649
650 #[test]
651 fn test_notification_data_default() {
652 let data = NotificationData::default();
653 assert!(data.id != 0); assert!(data.channel_id.is_none());
655 assert!(data.title.is_none());
656 assert!(data.body.is_none());
657 assert!(data.schedule.is_none());
658 assert!(!data.group_summary);
659 assert!(!data.ongoing);
660 assert!(!data.auto_cancel);
661 assert!(!data.silent);
662 assert!(data.inbox_lines.is_empty());
663 assert!(data.attachments.is_empty());
664 assert!(data.extra.is_empty());
665 }
666
667 #[test]
668 fn test_notification_data_serialization() {
669 let data = NotificationData {
670 id: 123,
671 title: Some("Test Title".to_string()),
672 body: Some("Test Body".to_string()),
673 ongoing: true,
674 ..Default::default()
675 };
676
677 let json = serde_json::to_string(&data).expect("Failed to serialize notification data");
678 assert!(json.contains("\"id\":123"));
679 assert!(json.contains("\"title\":\"Test Title\""));
680 assert!(json.contains("\"body\":\"Test Body\""));
681 assert!(json.contains("\"ongoing\":true"));
682 }
683
684 #[test]
685 fn test_pending_notification_getters() {
686 let json = r#"{
687 "id": 456,
688 "title": "Pending Title",
689 "body": "Pending Body",
690 "schedule": {"every": {"interval": "day", "count": 1}}
691 }"#;
692 let pending: PendingNotification =
693 serde_json::from_str(json).expect("Failed to deserialize pending notification");
694
695 assert_eq!(pending.id(), 456);
696 assert_eq!(pending.title(), Some("Pending Title"));
697 assert_eq!(pending.body(), Some("Pending Body"));
698 assert!(matches!(pending.schedule(), Schedule::Every { .. }));
699 }
700
701 #[test]
702 fn test_active_notification_getters() {
703 let json = r#"{
704 "id": 789,
705 "title": "Active Title",
706 "body": "Active Body",
707 "group": "test_group",
708 "groupSummary": true
709 }"#;
710 let active: ActiveNotification =
711 serde_json::from_str(json).expect("Failed to deserialize active notification");
712
713 assert_eq!(active.id(), 789);
714 assert_eq!(active.title(), Some("Active Title"));
715 assert_eq!(active.body(), Some("Active Body"));
716 assert_eq!(active.group(), Some("test_group"));
717 assert!(active.group_summary());
718 assert!(active.data().is_empty());
719 assert!(active.extra().is_empty());
720 assert!(active.attachments().is_empty());
721 assert!(active.action_type_id().is_none());
722 assert!(active.schedule().is_none());
723 assert!(active.sound().is_none());
724 }
725
726 #[cfg(target_os = "android")]
727 #[test]
728 fn test_importance_default() {
729 let importance = Importance::default();
730 assert!(matches!(importance, Importance::Default));
731 }
732
733 #[cfg(target_os = "android")]
734 #[test]
735 fn test_importance_serialization() {
736 assert_eq!(
737 serde_json::to_string(&Importance::None).expect("Failed to serialize Importance::None"),
738 "0"
739 );
740 assert_eq!(
741 serde_json::to_string(&Importance::Min).expect("Failed to serialize Importance::Min"),
742 "1"
743 );
744 assert_eq!(
745 serde_json::to_string(&Importance::Low).expect("Failed to serialize Importance::Low"),
746 "2"
747 );
748 assert_eq!(
749 serde_json::to_string(&Importance::Default)
750 .expect("Failed to serialize Importance::Default"),
751 "3"
752 );
753 assert_eq!(
754 serde_json::to_string(&Importance::High).expect("Failed to serialize Importance::High"),
755 "4"
756 );
757 }
758
759 #[cfg(target_os = "android")]
760 #[test]
761 fn test_visibility_serialization() {
762 assert_eq!(
763 serde_json::to_string(&Visibility::Secret)
764 .expect("Failed to serialize Visibility::Secret"),
765 "-1"
766 );
767 assert_eq!(
768 serde_json::to_string(&Visibility::Private)
769 .expect("Failed to serialize Visibility::Private"),
770 "0"
771 );
772 assert_eq!(
773 serde_json::to_string(&Visibility::Public)
774 .expect("Failed to serialize Visibility::Public"),
775 "1"
776 );
777 }
778
779 #[cfg(target_os = "android")]
780 #[test]
781 fn test_channel_builder() {
782 let channel = Channel::builder("test_id", "Test Channel")
783 .description("Test Description")
784 .sound("test_sound")
785 .lights(true)
786 .light_color("#FF0000")
787 .vibration(true)
788 .importance(Importance::High)
789 .visibility(Visibility::Public)
790 .build();
791
792 assert_eq!(channel.id(), "test_id");
793 assert_eq!(channel.name(), "Test Channel");
794 assert_eq!(channel.description(), Some("Test Description"));
795 assert_eq!(channel.sound(), Some("test_sound"));
796 assert!(channel.lights());
797 assert_eq!(channel.light_color(), Some("#FF0000"));
798 assert!(channel.vibration());
799 assert!(matches!(channel.importance(), Importance::High));
800 assert_eq!(channel.visibility(), Some(Visibility::Public));
801 }
802
803 #[cfg(target_os = "android")]
804 #[test]
805 fn test_channel_builder_minimal() {
806 let channel = Channel::builder("minimal_id", "Minimal Channel").build();
807
808 assert_eq!(channel.id(), "minimal_id");
809 assert_eq!(channel.name(), "Minimal Channel");
810 assert_eq!(channel.description(), None);
811 assert_eq!(channel.sound(), None);
812 assert!(!channel.lights());
813 assert_eq!(channel.light_color(), None);
814 assert!(!channel.vibration());
815 assert!(matches!(channel.importance(), Importance::Default));
816 assert_eq!(channel.visibility(), None);
817 }
818
819 #[test]
820 fn test_schedule_at_serialization() {
821 use time::OffsetDateTime;
822
823 let date = OffsetDateTime::now_utc();
824 let schedule = Schedule::At {
825 date,
826 repeating: true,
827 allow_while_idle: false,
828 };
829
830 let json = serde_json::to_string(&schedule).expect("Failed to serialize Schedule::At");
831 assert!(json.contains("\"at\""));
832 assert!(json.contains("\"date\""));
833 assert!(json.contains("\"repeating\":true"));
834 assert!(json.contains("\"allowWhileIdle\":false"));
835 }
836
837 #[test]
838 fn test_schedule_interval_variant() {
839 let schedule = Schedule::Interval {
840 interval: ScheduleInterval {
841 hour: Some(10),
842 minute: Some(30),
843 ..Default::default()
844 },
845 allow_while_idle: true,
846 };
847
848 let json =
849 serde_json::to_string(&schedule).expect("Failed to serialize Schedule::Interval");
850 assert!(json.contains("\"interval\""));
851 assert!(json.contains("\"hour\":10"));
852 assert!(json.contains("\"minute\":30"));
853 assert!(json.contains("\"allowWhileIdle\":true"));
854 }
855
856 #[test]
857 fn test_schedule_every_variant() {
858 let schedule = Schedule::Every {
859 interval: ScheduleEvery::Day,
860 count: 5,
861 allow_while_idle: false,
862 };
863
864 let json = serde_json::to_string(&schedule).expect("Failed to serialize Schedule::Every");
865 assert!(json.contains("\"every\""));
866 assert!(json.contains("\"interval\":\"day\""));
867 assert!(json.contains("\"count\":5"));
868 }
869}