diff --git a/assets/icons/maximize.svg b/assets/icons/maximize.svg index f37f6a2087f968728170539b379206cca7551b0e..b3504b5701e3081f82c4fc0ee2ec89642fb439c4 100644 --- a/assets/icons/maximize.svg +++ b/assets/icons/maximize.svg @@ -1,4 +1 @@ - - - - + diff --git a/assets/icons/menu.svg b/assets/icons/menu.svg index 060caeecbfd58603530f253248a0c369ba329b4e..6598697ff83fbb54e760711e569c26f5fac6776f 100644 --- a/assets/icons/menu.svg +++ b/assets/icons/menu.svg @@ -1,3 +1 @@ - - - + diff --git a/assets/icons/minimize.svg b/assets/icons/minimize.svg index ec78f152e13eda0c887a18b99b585d0c65acc8a8..0451233cc9b5455396d7655f8a9b1bc4791d0fc7 100644 --- a/assets/icons/minimize.svg +++ b/assets/icons/minimize.svg @@ -1,4 +1 @@ - - - - + diff --git a/assets/icons/quote.svg b/assets/icons/quote.svg index 50205479c300e789b302cb1dcd687aaf7f9353f8..b970db14300b1837691d346498e8af989442e721 100644 --- a/assets/icons/quote.svg +++ b/assets/icons/quote.svg @@ -1,8 +1 @@ - - - + diff --git a/assets/icons/snip.svg b/assets/icons/snip.svg new file mode 100644 index 0000000000000000000000000000000000000000..03ae4ce0399fae65f847224aa26695530dbd1a66 --- /dev/null +++ b/assets/icons/snip.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/split_message.svg b/assets/icons/split_message.svg deleted file mode 100644 index 54d9e81224cbf55eca2a4f354f7fcfc8f98b6854..0000000000000000000000000000000000000000 --- a/assets/icons/split_message.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs index 79ebb6602d77776ee30f905cacba3ce6434e9512..ec67d684da92fc85be4fcb5efecc040c783901ed 100644 --- a/crates/assistant2/src/assistant_panel.rs +++ b/crates/assistant2/src/assistant_panel.rs @@ -54,7 +54,9 @@ use std::{ }; use theme::ThemeSettings; use ui::{ - h_stack, prelude::*, v_stack, Button, ButtonLike, Icon, IconButton, IconElement, Label, Tooltip, + prelude::*, + utils::{DateTimeType, FormatDistance}, + ButtonLike, Tab, TabBar, Tooltip, }; use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt}; use uuid::Uuid; @@ -939,7 +941,7 @@ impl AssistantPanel { this.set_active_editor_index(this.prev_active_editor_index, cx); } })) - .tooltip(|cx| Tooltip::text("History", cx)) + .tooltip(|cx| Tooltip::text("Conversation History", cx)) } fn render_editor_tools(&self, cx: &mut ViewContext) -> Vec { @@ -955,12 +957,13 @@ impl AssistantPanel { } fn render_split_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("split_button", Icon::SplitMessage) + IconButton::new("split_button", Icon::Snip) .on_click(cx.listener(|this, _event, cx| { if let Some(active_editor) = this.active_editor() { active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx)); } })) + .icon_size(IconSize::Small) .tooltip(|cx| Tooltip::for_action("Split Message", &Split, cx)) } @@ -971,6 +974,7 @@ impl AssistantPanel { active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx)); } })) + .icon_size(IconSize::Small) .tooltip(|cx| Tooltip::for_action("Assist", &Assist, cx)) } @@ -985,6 +989,7 @@ impl AssistantPanel { }); } })) + .icon_size(IconSize::Small) .tooltip(|cx| Tooltip::for_action("Quote Seleciton", &QuoteSelection, cx)) } @@ -993,15 +998,19 @@ impl AssistantPanel { .on_click(cx.listener(|this, _event, cx| { this.new_conversation(cx); })) + .icon_size(IconSize::Small) .tooltip(|cx| Tooltip::for_action("New Conversation", &NewConversation, cx)) } fn render_zoom_button(&self, cx: &mut ViewContext) -> impl IntoElement { let zoomed = self.zoomed; - IconButton::new("zoom_button", Icon::MagnifyingGlass) + IconButton::new("zoom_button", Icon::Maximize) .on_click(cx.listener(|this, _event, cx| { this.toggle_zoom(&ToggleZoom, cx); })) + .selected(zoomed) + .selected_icon(Icon::Minimize) + .icon_size(IconSize::Small) .tooltip(move |cx| { Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx) }) @@ -1020,10 +1029,19 @@ impl AssistantPanel { this.open_conversation(path.clone(), cx) .detach_and_log_err(cx) })) - .child(Label::new( - conversation.mtime.format("%F %I:%M%p").to_string(), - )) - .child(Label::new(conversation.title.clone())) + .full_width() + .child( + div() + .flex() + .w_full() + .gap_2() + .child( + Label::new(conversation.mtime.format("%F %I:%M%p").to_string()) + .color(Color::Muted) + .size(LabelSize::Small), + ) + .child(Label::new(conversation.title.clone()).size(LabelSize::Small)), + ) } fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext) -> Task> { @@ -1112,20 +1130,35 @@ impl Render for AssistantPanel { .border() .border_color(gpui::red()) } else { - let title = self - .active_editor() - .map(|editor| Label::new(editor.read(cx).title(cx))); - - let mut header = h_stack() - .child(Self::render_hamburger_button(cx)) - .children(title); - - if self.focus_handle.contains_focused(cx) { - header = header - .children(self.render_editor_tools(cx)) - .child(Self::render_plus_button(cx)) - .child(self.render_zoom_button(cx)); - } + let header = TabBar::new("assistant_header") + .start_child( + h_stack().gap_1().child(Self::render_hamburger_button(cx)), // .children(title), + ) + .children(self.active_editor().map(|editor| { + h_stack() + .h(rems(Tab::HEIGHT_IN_REMS)) + .flex_1() + .px_2() + .child(Label::new(editor.read(cx).title(cx)).into_element()) + })) + .end_child(if self.focus_handle.contains_focused(cx) { + h_stack() + .gap_2() + .child(h_stack().gap_1().children(self.render_editor_tools(cx))) + .child( + ui::Divider::vertical() + .inset() + .color(ui::DividerColor::Border), + ) + .child( + h_stack() + .gap_1() + .child(Self::render_plus_button(cx)) + .child(self.render_zoom_button(cx)), + ) + } else { + div() + }); v_stack() .size_full() @@ -1165,8 +1198,6 @@ impl Render for AssistantPanel { .into_any_element() }), ) - .border() - .border_color(gpui::red()) } } } @@ -2251,6 +2282,14 @@ impl ConversationEditor { } Role::System => Label::new("System").color(Color::Warning), }) + .tooltip(|cx| { + Tooltip::with_meta( + "Toggle message role", + None, + "Available roles: You (User), Assistant, System", + cx, + ) + }) .on_click({ let conversation = conversation.clone(); move |_, cx| { @@ -2265,10 +2304,22 @@ impl ConversationEditor { h_stack() .id(("message_header", message_id.0)) - .border() - .border_color(gpui::red()) + .h_11() + .gap_1() + .p_1() .child(sender) - .child(Label::new(message.sent_at.format("%I:%M%P").to_string())) + // TODO: Only show this if the message if the message has been sent + .child( + Label::new( + FormatDistance::from_now(DateTimeType::Local( + message.sent_at, + )) + .hide_prefix(true) + .add_suffix(true) + .to_string(), + ) + .color(Color::Muted), + ) .children( if let MessageStatus::Error(error) = message.status.clone() { Some( @@ -2429,6 +2480,7 @@ impl ConversationEditor { "current_model", self.conversation.read(cx).model.short_name(), ) + .style(ButtonStyle::Filled) .tooltip(move |cx| Tooltip::text("Change Model", cx)) .on_click(cx.listener(|this, _, cx| this.cycle_model(cx))) } @@ -2442,12 +2494,7 @@ impl ConversationEditor { } else { Color::Default }; - Some( - div() - .border() - .border_color(gpui::red()) - .child(Label::new(remaining_tokens.to_string()).color(remaining_tokens_color)), - ) + Some(Label::new(remaining_tokens.to_string()).color(remaining_tokens_color)) } } @@ -2459,15 +2506,21 @@ impl Render for ConversationEditor { fn render(&mut self, cx: &mut ViewContext) -> Self::Element { div() .key_context("ConversationEditor") - .size_full() - .relative() .capture_action(cx.listener(ConversationEditor::cancel_last_assist)) .capture_action(cx.listener(ConversationEditor::save)) .capture_action(cx.listener(ConversationEditor::copy)) .capture_action(cx.listener(ConversationEditor::cycle_message_role)) .on_action(cx.listener(ConversationEditor::assist)) .on_action(cx.listener(ConversationEditor::split)) - .child(self.editor.clone()) + .size_full() + .relative() + .child( + div() + .size_full() + .pl_2() + .bg(cx.theme().colors().editor_background) + .child(self.editor.clone()), + ) .child( h_stack() .absolute() diff --git a/crates/ui2/src/components/divider.rs b/crates/ui2/src/components/divider.rs index cb48ce00ae24f031807d8522510aa18bc586d638..20744d6c48e94ea80312acd9bf3ef63b7a30496f 100644 --- a/crates/ui2/src/components/divider.rs +++ b/crates/ui2/src/components/divider.rs @@ -1,4 +1,4 @@ -use gpui::{Div, IntoElement}; +use gpui::{Div, Hsla, IntoElement}; use crate::prelude::*; @@ -7,9 +7,26 @@ enum DividerDirection { Vertical, } +#[derive(Default)] +pub enum DividerColor { + Border, + #[default] + BorderVariant, +} + +impl DividerColor { + pub fn hsla(self, cx: &WindowContext) -> Hsla { + match self { + DividerColor::Border => cx.theme().colors().border, + DividerColor::BorderVariant => cx.theme().colors().border_variant, + } + } +} + #[derive(IntoElement)] pub struct Divider { direction: DividerDirection, + color: DividerColor, inset: bool, } @@ -26,7 +43,7 @@ impl RenderOnce for Divider { this.w_px().h_full().when(self.inset, |this| this.my_1p5()) } }) - .bg(cx.theme().colors().border_variant) + .bg(self.color.hsla(cx)) } } @@ -34,6 +51,7 @@ impl Divider { pub fn horizontal() -> Self { Self { direction: DividerDirection::Horizontal, + color: DividerColor::default(), inset: false, } } @@ -41,6 +59,7 @@ impl Divider { pub fn vertical() -> Self { Self { direction: DividerDirection::Vertical, + color: DividerColor::default(), inset: false, } } @@ -49,4 +68,9 @@ impl Divider { self.inset = true; self } + + pub fn color(mut self, color: DividerColor) -> Self { + self.color = color; + self + } } diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs index 6216acac483489f283366177fe5b6416495122c9..ca50cae7f8936e50dbc66be2def222589a571207 100644 --- a/crates/ui2/src/components/icon.rs +++ b/crates/ui2/src/components/icon.rs @@ -75,6 +75,7 @@ pub enum Icon { MagnifyingGlass, MailOpen, Maximize, + Minimize, Menu, MessageBubbles, Mic, @@ -88,7 +89,7 @@ pub enum Icon { Screen, SelectAll, Split, - SplitMessage, + Snip, Terminal, WholeWord, XCircle, @@ -156,6 +157,7 @@ impl Icon { Icon::MagnifyingGlass => "icons/magnifying_glass.svg", Icon::MailOpen => "icons/mail-open.svg", Icon::Maximize => "icons/maximize.svg", + Icon::Minimize => "icons/minimize.svg", Icon::Menu => "icons/menu.svg", Icon::MessageBubbles => "icons/conversations.svg", Icon::Mic => "icons/mic.svg", @@ -169,7 +171,7 @@ impl Icon { Icon::Screen => "icons/desktop.svg", Icon::SelectAll => "icons/select-all.svg", Icon::Split => "icons/split.svg", - Icon::SplitMessage => "icons/split_message.svg", + Icon::Snip => "icons/snip.svg", Icon::Terminal => "icons/terminal.svg", Icon::WholeWord => "icons/word_search.svg", Icon::XCircle => "icons/error.svg", diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index 8114a322e300cac465981438bf8ad02d84ca029e..eddfab811914b721126ccf777c9818498e405569 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -48,6 +48,8 @@ impl Tab { } } + pub const HEIGHT_IN_REMS: f32 = 30. / 16.; + pub fn position(mut self, position: TabPosition) -> Self { self.position = position; self @@ -94,8 +96,6 @@ impl RenderOnce for Tab { type Rendered = Stateful
; fn render(self, cx: &mut WindowContext) -> Self::Rendered { - const HEIGHT_IN_REMS: f32 = 30. / 16.; - let (text_color, tab_bg, _tab_hover_bg, _tab_active_bg) = match self.selected { false => ( cx.theme().colors().text_muted, @@ -112,7 +112,7 @@ impl RenderOnce for Tab { }; self.div - .h(rems(HEIGHT_IN_REMS)) + .h(rems(Self::HEIGHT_IN_REMS)) .bg(tab_bg) .border_color(cx.theme().colors().border) .map(|this| match self.position { diff --git a/crates/ui2/src/utils/format_distance.rs b/crates/ui2/src/utils/format_distance.rs index d157d30bbebfeb28103c9aea3cc6193253f81e93..b0fce184ce64e23e1173ddcce77f3532fb0dfe5e 100644 --- a/crates/ui2/src/utils/format_distance.rs +++ b/crates/ui2/src/utils/format_distance.rs @@ -1,4 +1,72 @@ -use chrono::NaiveDateTime; +use chrono::{DateTime, Local, NaiveDateTime}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum DateTimeType { + Naive(NaiveDateTime), + Local(DateTime), +} + +impl DateTimeType { + /// Converts the DateTimeType to a NaiveDateTime. + /// + /// If the DateTimeType is already a NaiveDateTime, it will be returned as is. + /// If the DateTimeType is a DateTime, it will be converted to a NaiveDateTime. + pub fn to_naive(&self) -> NaiveDateTime { + match self { + DateTimeType::Naive(naive) => *naive, + DateTimeType::Local(local) => local.naive_local(), + } + } +} + +pub struct FormatDistance { + date: DateTimeType, + base_date: DateTimeType, + include_seconds: bool, + add_suffix: bool, + hide_prefix: bool, +} + +impl FormatDistance { + pub fn new(date: DateTimeType, base_date: DateTimeType) -> Self { + Self { + date, + base_date, + include_seconds: false, + add_suffix: false, + hide_prefix: false, + } + } + + pub fn from_now(date: DateTimeType) -> Self { + Self::new(date, DateTimeType::Local(Local::now())) + } + + pub fn to_string(self) -> String { + format_distance( + self.date, + self.base_date.to_naive(), + self.include_seconds, + self.add_suffix, + self.hide_prefix, + ) + } + + pub fn include_seconds(mut self, include_seconds: bool) -> Self { + self.include_seconds = include_seconds; + self + } + + pub fn add_suffix(mut self, add_suffix: bool) -> Self { + self.add_suffix = add_suffix; + self + } + + pub fn hide_prefix(mut self, hide_prefix: bool) -> Self { + self.hide_prefix = hide_prefix; + self + } +} /// Calculates the distance in seconds between two NaiveDateTime objects. /// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative. @@ -13,7 +81,12 @@ fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 { } /// Generates a string describing the time distance between two dates in a human-readable way. -fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String { +fn distance_string( + distance: i64, + include_seconds: bool, + add_suffix: bool, + hide_prefix: bool, +) -> String { let suffix = if distance < 0 { " from now" } else { " ago" }; let distance = distance.abs(); @@ -24,53 +97,128 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St let months = distance / 2_592_000; let string = if distance < 5 && include_seconds { - "less than 5 seconds".to_string() + if hide_prefix { + "5 seconds" + } else { + "less than 5 seconds" + } + .to_string() } else if distance < 10 && include_seconds { - "less than 10 seconds".to_string() + if hide_prefix { + "10 seconds" + } else { + "less than 10 seconds" + } + .to_string() } else if distance < 20 && include_seconds { - "less than 20 seconds".to_string() + if hide_prefix { + "20 seconds" + } else { + "less than 20 seconds" + } + .to_string() } else if distance < 40 && include_seconds { - "half a minute".to_string() + if hide_prefix { + "half a minute" + } else { + "half a minute" + } + .to_string() } else if distance < 60 && include_seconds { - "less than a minute".to_string() + if hide_prefix { + "a minute" + } else { + "less than a minute" + } + .to_string() } else if distance < 90 && include_seconds { "1 minute".to_string() } else if distance < 30 { - "less than a minute".to_string() + if hide_prefix { + "a minute" + } else { + "less than a minute" + } + .to_string() } else if distance < 90 { "1 minute".to_string() } else if distance < 2_700 { format!("{} minutes", minutes) } else if distance < 5_400 { - "about 1 hour".to_string() + if hide_prefix { + "1 hour" + } else { + "about 1 hour" + } + .to_string() } else if distance < 86_400 { - format!("about {} hours", hours) + if hide_prefix { + format!("{} hours", hours) + } else { + format!("about {} hours", hours) + } + .to_string() } else if distance < 172_800 { "1 day".to_string() } else if distance < 2_592_000 { format!("{} days", days) } else if distance < 5_184_000 { - "about 1 month".to_string() + if hide_prefix { + "1 month" + } else { + "about 1 month" + } + .to_string() } else if distance < 7_776_000 { - "about 2 months".to_string() + if hide_prefix { + "2 months" + } else { + "about 2 months" + } + .to_string() } else if distance < 31_540_000 { format!("{} months", months) } else if distance < 39_425_000 { - "about 1 year".to_string() + if hide_prefix { + "1 year" + } else { + "about 1 year" + } + .to_string() } else if distance < 55_195_000 { - "over 1 year".to_string() + if hide_prefix { "1 year" } else { "over 1 year" }.to_string() } else if distance < 63_080_000 { - "almost 2 years".to_string() + if hide_prefix { + "2 years" + } else { + "almost 2 years" + } + .to_string() } else { let years = distance / 31_536_000; let remaining_months = (distance % 31_536_000) / 2_592_000; if remaining_months < 3 { - format!("about {} years", years) + if hide_prefix { + format!("{} years", years) + } else { + format!("about {} years", years) + } + .to_string() } else if remaining_months < 9 { - format!("over {} years", years) + if hide_prefix { + format!("{} years", years) + } else { + format!("over {} years", years) + } + .to_string() } else { - format!("almost {} years", years + 1) + if hide_prefix { + format!("{} years", years + 1) + } else { + format!("almost {} years", years + 1) + } + .to_string() } }; @@ -108,15 +256,16 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St /// ``` /// /// Output: `"There was about 3 years between the first and last crewed moon landings."` -pub fn naive_format_distance( - date: NaiveDateTime, +pub fn format_distance( + date: DateTimeType, base_date: NaiveDateTime, include_seconds: bool, add_suffix: bool, + hide_prefix: bool, ) -> String { - let distance = distance_in_seconds(date, base_date); + let distance = distance_in_seconds(date.to_naive(), base_date); - distance_string(distance, include_seconds, add_suffix) + distance_string(distance, include_seconds, add_suffix, hide_prefix) } /// Get the time difference between a date and now as relative human readable string. @@ -142,14 +291,15 @@ pub fn naive_format_distance( /// ``` /// /// Output: `It's been over 54 years since Apollo 11 first landed on the moon.` -pub fn naive_format_distance_from_now( - datetime: NaiveDateTime, +pub fn format_distance_from_now( + datetime: DateTimeType, include_seconds: bool, add_suffix: bool, + hide_prefix: bool, ) -> String { let now = chrono::offset::Local::now().naive_local(); - naive_format_distance(datetime, now, include_seconds, add_suffix) + format_distance(datetime, now, include_seconds, add_suffix, hide_prefix) } #[cfg(test)] @@ -158,73 +308,127 @@ mod tests { use chrono::NaiveDateTime; #[test] - fn test_naive_format_distance() { - let date = - NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date"); - let base_date = - NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date"); + fn test_format_distance() { + let date = DateTimeType::Naive( + NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date"), + ); + let base_date = DateTimeType::Naive( + NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date"), + ); assert_eq!( "about 2 hours", - naive_format_distance(date, base_date, false, false) + format_distance(date, base_date.to_naive(), false, false, false) ); } #[test] - fn test_naive_format_distance_with_suffix() { - let date = - NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date"); - let base_date = - NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date"); + fn test_format_distance_with_suffix() { + let date = DateTimeType::Naive( + NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date"), + ); + let base_date = DateTimeType::Naive( + NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date"), + ); assert_eq!( "about 2 hours from now", - naive_format_distance(date, base_date, false, true) + format_distance(date, base_date.to_naive(), false, true, false) ); } #[test] - fn test_naive_format_distance_from_now() { - let date = NaiveDateTime::parse_from_str("1969-07-20T00:00:00Z", "%Y-%m-%dT%H:%M:%SZ") - .expect("Invalid NaiveDateTime for date"); + fn test_format_distance_from_now() { + let date = DateTimeType::Naive( + NaiveDateTime::parse_from_str("1969-07-20T00:00:00Z", "%Y-%m-%dT%H:%M:%SZ") + .expect("Invalid NaiveDateTime for date"), + ); assert_eq!( "over 54 years ago", - naive_format_distance_from_now(date, false, true) + format_distance_from_now(date, false, true, false) ); } #[test] - fn test_naive_format_distance_string() { - assert_eq!(distance_string(3, false, false), "less than a minute"); - assert_eq!(distance_string(7, false, false), "less than a minute"); - assert_eq!(distance_string(13, false, false), "less than a minute"); - assert_eq!(distance_string(21, false, false), "less than a minute"); - assert_eq!(distance_string(45, false, false), "1 minute"); - assert_eq!(distance_string(61, false, false), "1 minute"); - assert_eq!(distance_string(1920, false, false), "32 minutes"); - assert_eq!(distance_string(3902, false, false), "about 1 hour"); - assert_eq!(distance_string(18002, false, false), "about 5 hours"); - assert_eq!(distance_string(86470, false, false), "1 day"); - assert_eq!(distance_string(345880, false, false), "4 days"); - assert_eq!(distance_string(2764800, false, false), "about 1 month"); - assert_eq!(distance_string(5184000, false, false), "about 2 months"); - assert_eq!(distance_string(10368000, false, false), "4 months"); - assert_eq!(distance_string(34694000, false, false), "about 1 year"); - assert_eq!(distance_string(47310000, false, false), "over 1 year"); - assert_eq!(distance_string(61503000, false, false), "almost 2 years"); - assert_eq!(distance_string(160854000, false, false), "about 5 years"); - assert_eq!(distance_string(236550000, false, false), "over 7 years"); - assert_eq!(distance_string(249166000, false, false), "almost 8 years"); + fn test_format_distance_string() { + assert_eq!( + distance_string(3, false, false, false), + "less than a minute" + ); + assert_eq!( + distance_string(7, false, false, false), + "less than a minute" + ); + assert_eq!( + distance_string(13, false, false, false), + "less than a minute" + ); + assert_eq!( + distance_string(21, false, false, false), + "less than a minute" + ); + assert_eq!(distance_string(45, false, false, false), "1 minute"); + assert_eq!(distance_string(61, false, false, false), "1 minute"); + assert_eq!(distance_string(1920, false, false, false), "32 minutes"); + assert_eq!(distance_string(3902, false, false, false), "about 1 hour"); + assert_eq!(distance_string(18002, false, false, false), "about 5 hours"); + assert_eq!(distance_string(86470, false, false, false), "1 day"); + assert_eq!(distance_string(345880, false, false, false), "4 days"); + assert_eq!( + distance_string(2764800, false, false, false), + "about 1 month" + ); + assert_eq!( + distance_string(5184000, false, false, false), + "about 2 months" + ); + assert_eq!(distance_string(10368000, false, false, false), "4 months"); + assert_eq!( + distance_string(34694000, false, false, false), + "about 1 year" + ); + assert_eq!( + distance_string(47310000, false, false, false), + "over 1 year" + ); + assert_eq!( + distance_string(61503000, false, false, false), + "almost 2 years" + ); + assert_eq!( + distance_string(160854000, false, false, false), + "about 5 years" + ); + assert_eq!( + distance_string(236550000, false, false, false), + "over 7 years" + ); + assert_eq!( + distance_string(249166000, false, false, false), + "almost 8 years" + ); } #[test] - fn test_naive_format_distance_string_include_seconds() { - assert_eq!(distance_string(3, true, false), "less than 5 seconds"); - assert_eq!(distance_string(7, true, false), "less than 10 seconds"); - assert_eq!(distance_string(13, true, false), "less than 20 seconds"); - assert_eq!(distance_string(21, true, false), "half a minute"); - assert_eq!(distance_string(45, true, false), "less than a minute"); - assert_eq!(distance_string(61, true, false), "1 minute"); + fn test_format_distance_string_include_seconds() { + assert_eq!( + distance_string(3, true, false, false), + "less than 5 seconds" + ); + assert_eq!( + distance_string(7, true, false, false), + "less than 10 seconds" + ); + assert_eq!( + distance_string(13, true, false, false), + "less than 20 seconds" + ); + assert_eq!(distance_string(21, true, false, false), "half a minute"); + assert_eq!( + distance_string(45, true, false, false), + "less than a minute" + ); + assert_eq!(distance_string(61, true, false, false), "1 minute"); } }