58
crates/domain/tests/entry/date_test.rs
Normal file
58
crates/domain/tests/entry/date_test.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
|
||||
use domain::entry::Date;
|
||||
use domain::user::Timezone;
|
||||
|
||||
fn instant(text: &str) -> DateTime<FixedOffset> {
|
||||
text.parse().unwrap()
|
||||
}
|
||||
|
||||
fn zone(name: &str) -> Timezone {
|
||||
Timezone::new(name).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_instant_resolves_to_the_calendar_day_in_the_users_zone() {
|
||||
let date = Date::from_instant(
|
||||
&instant("2025-03-15T09:00:00+13:00"),
|
||||
&zone("Pacific/Auckland"),
|
||||
);
|
||||
|
||||
assert_eq!(date.value().to_string(), "2025-03-15");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_users_zone_decides_the_day_not_the_offset_the_client_sent() {
|
||||
let same_moment = instant("2025-03-15T23:00:00+00:00");
|
||||
|
||||
let in_auckland = Date::from_instant(&same_moment, &zone("Pacific/Auckland"));
|
||||
let in_warsaw = Date::from_instant(&same_moment, &zone("Europe/Warsaw"));
|
||||
|
||||
assert_eq!(in_auckland.value().to_string(), "2025-03-16");
|
||||
assert_eq!(in_warsaw.value().to_string(), "2025-03-16");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_evening_in_a_far_eastern_zone_is_not_the_utc_day() {
|
||||
let evening = instant("2025-03-14T20:00:00+00:00");
|
||||
|
||||
let date = Date::from_instant(&evening, &zone("Pacific/Auckland"));
|
||||
|
||||
assert_eq!(date.value().to_string(), "2025-03-15");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_day_knows_the_day_before_it() {
|
||||
let date = Date::from_instant(&instant("2025-03-01T12:00:00+00:00"), &zone("UTC"));
|
||||
|
||||
assert_eq!(date.previous().value().to_string(), "2025-02-28");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn days_between_two_dates_is_their_gap() {
|
||||
let earlier = Date::from_instant(&instant("2025-03-01T12:00:00+00:00"), &zone("UTC"));
|
||||
let later = Date::from_instant(&instant("2025-03-05T12:00:00+00:00"), &zone("UTC"));
|
||||
|
||||
assert_eq!(later.days_since(&earlier), 4);
|
||||
assert_eq!(earlier.days_since(&later), -4);
|
||||
}
|
||||
57
crates/domain/tests/entry/day_mood_test.rs
Normal file
57
crates/domain/tests/entry/day_mood_test.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use domain::entry::{DayMood, Mood};
|
||||
|
||||
#[test]
|
||||
fn a_day_of_one_awful_and_one_rad_sits_in_the_middle() {
|
||||
let day = DayMood::of(&[Mood::Awful, Mood::Rad]).unwrap();
|
||||
|
||||
assert!((day.value() - 3.0).abs() < f64::EPSILON);
|
||||
assert_eq!(day.rounded(), Mood::Meh);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_day_with_no_entries_has_no_mood() {
|
||||
assert!(DayMood::of(&[]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_bad_day_among_four_middling_ones_still_shows() {
|
||||
let mostly_meh = DayMood::of(&[Mood::Meh, Mood::Meh, Mood::Meh, Mood::Meh, Mood::Awful])
|
||||
.unwrap()
|
||||
.value();
|
||||
let all_meh = DayMood::of(&[Mood::Meh, Mood::Meh, Mood::Meh, Mood::Meh, Mood::Meh])
|
||||
.unwrap()
|
||||
.value();
|
||||
|
||||
assert!(mostly_meh < all_meh);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_single_entry_is_the_whole_day() {
|
||||
let day = DayMood::of(&[Mood::Bad]).unwrap();
|
||||
|
||||
assert!((day.value() - 2.0).abs() < f64::EPSILON);
|
||||
assert_eq!(day.rounded(), Mood::Bad);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_day_exactly_between_two_moods_shows_the_better_one() {
|
||||
let day = DayMood::of(&[Mood::Meh, Mood::Good]).unwrap();
|
||||
|
||||
assert!((day.value() - 3.5).abs() < f64::EPSILON);
|
||||
assert_eq!(day.rounded(), Mood::Good);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_whole_mean_rounds_to_its_own_mood() {
|
||||
let pairs = [
|
||||
(Mood::Awful, Mood::Awful),
|
||||
(Mood::Bad, Mood::Bad),
|
||||
(Mood::Meh, Mood::Meh),
|
||||
(Mood::Good, Mood::Good),
|
||||
(Mood::Rad, Mood::Rad),
|
||||
];
|
||||
|
||||
for (logged, expected) in pairs {
|
||||
assert_eq!(DayMood::of(&[logged, logged]).unwrap().rounded(), expected);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
use chrono::{FixedOffset, TimeZone, Utc};
|
||||
|
||||
use domain::activity::ActivityId;
|
||||
use domain::attachment::{PhotoId, VoiceMemoId};
|
||||
use domain::entry::{Content, Mood, MoodEntry, MoodEntryData};
|
||||
use domain::entry::{Mood, MoodEntry, MoodEntryData};
|
||||
use domain::user::UserId;
|
||||
|
||||
fn sample_logged_at() -> chrono::DateTime<FixedOffset> {
|
||||
@@ -19,10 +17,6 @@ fn new_entry_has_required_fields() {
|
||||
|
||||
assert_eq!(*entry.user_id(), user_id);
|
||||
assert_eq!(entry.mood(), Mood::Good);
|
||||
assert!(entry.activities().is_empty());
|
||||
assert!(entry.content().is_none());
|
||||
assert!(entry.photos().is_empty());
|
||||
assert!(entry.voice_memos().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -36,39 +30,10 @@ fn update_mood_changes_value_and_touches_updated_at() {
|
||||
assert!(*entry.updated_at() >= before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_activities_deduplicates_and_sorts() {
|
||||
let mut entry = MoodEntry::new(UserId::generate(), Mood::Meh, sample_logged_at());
|
||||
let a = ActivityId::generate();
|
||||
let b = ActivityId::generate();
|
||||
|
||||
entry.set_activities(vec![b.clone(), a.clone(), b.clone()]);
|
||||
|
||||
assert_eq!(entry.activities().len(), 2);
|
||||
let ids = entry.activities();
|
||||
assert!(ids[0] <= ids[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_content_updates_and_clears() {
|
||||
let mut entry = MoodEntry::new(UserId::generate(), Mood::Good, sample_logged_at());
|
||||
|
||||
let content = Content::new("great day").unwrap();
|
||||
entry.set_content(Some(content));
|
||||
assert!(entry.content().is_some());
|
||||
|
||||
entry.set_content(None);
|
||||
assert!(entry.content().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_persistence_reconstructs_all_fields() {
|
||||
let id = domain::entry::MoodEntryId::generate();
|
||||
let user_id = UserId::generate();
|
||||
let activity_id = ActivityId::generate();
|
||||
let photo_id = PhotoId::generate();
|
||||
let voice_memo_id = VoiceMemoId::generate();
|
||||
let content = Content::new("test").unwrap();
|
||||
let now = Utc::now();
|
||||
|
||||
let entry = MoodEntry::from_persistence(MoodEntryData {
|
||||
@@ -76,10 +41,6 @@ fn from_persistence_reconstructs_all_fields() {
|
||||
user_id: user_id.clone(),
|
||||
mood: Mood::Bad,
|
||||
logged_at: sample_logged_at(),
|
||||
activities: vec![activity_id.clone()],
|
||||
content: Some(content),
|
||||
photos: vec![photo_id.clone()],
|
||||
voice_memos: vec![voice_memo_id.clone()],
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
@@ -87,10 +48,7 @@ fn from_persistence_reconstructs_all_fields() {
|
||||
assert_eq!(*entry.id(), id);
|
||||
assert_eq!(*entry.user_id(), user_id);
|
||||
assert_eq!(entry.mood(), Mood::Bad);
|
||||
assert_eq!(entry.activities().len(), 1);
|
||||
assert!(entry.content().is_some());
|
||||
assert_eq!(entry.photos().len(), 1);
|
||||
assert_eq!(entry.voice_memos().len(), 1);
|
||||
assert_eq!(*entry.logged_at(), sample_logged_at());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -105,32 +63,3 @@ fn update_logged_at_changes_timestamp() {
|
||||
|
||||
assert_eq!(*entry.logged_at(), new_time);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_photos_replaces_list() {
|
||||
let mut entry = MoodEntry::new(UserId::generate(), Mood::Good, sample_logged_at());
|
||||
|
||||
let p1 = PhotoId::generate();
|
||||
let p2 = PhotoId::generate();
|
||||
entry.set_photos(vec![p1.clone(), p2.clone()]);
|
||||
assert_eq!(entry.photos().len(), 2);
|
||||
|
||||
entry.set_photos(vec![p1.clone()]);
|
||||
assert_eq!(entry.photos().len(), 1);
|
||||
|
||||
entry.set_photos(vec![]);
|
||||
assert!(entry.photos().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_voice_memos_replaces_list() {
|
||||
let mut entry = MoodEntry::new(UserId::generate(), Mood::Good, sample_logged_at());
|
||||
|
||||
let m1 = VoiceMemoId::generate();
|
||||
let m2 = VoiceMemoId::generate();
|
||||
entry.set_voice_memos(vec![m1.clone(), m2.clone()]);
|
||||
assert_eq!(entry.voice_memos().len(), 2);
|
||||
|
||||
entry.set_voice_memos(vec![]);
|
||||
assert!(entry.voice_memos().is_empty());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user