Extract pure logic out of RSX components into unit-testable handlers modules #9

Merged
schaefera merged 4 commits from worktree-extract-rsx-logic into main 2026-09-15 09:11:51 +00:00
2 changed files with 43 additions and 11 deletions
Showing only changes of commit bd086b0288 - Show all commits

View file

@ -1,4 +1,4 @@
use super::handlers::{normalize_feed_url, subscribed_message}; use super::handlers::{normalize_feed_url, resolve_submit_outcome, SubmitOutcome};
use crate::api; use crate::api;
use crate::components::button::{Button, ButtonVariant}; use crate::components::button::{Button, ButtonVariant};
use crate::components::input::Input; use crate::components::input::Input;
@ -20,13 +20,14 @@ pub fn SubscribeForm(on_subscribed: EventHandler<()>) -> Element {
spawn(async move { spawn(async move {
submitting.set(true); submitting.set(true);
status.set(None); status.set(None);
match api::feeds::subscribe_feed(feed_url).await { let result = api::feeds::subscribe_feed(feed_url).await;
Ok(count) => { match resolve_submit_outcome(result.map_err(|err| err.to_string())) {
status.set(Some(Ok(subscribed_message(count)))); SubmitOutcome::Success { message } => {
status.set(Some(Ok(message)));
url.set(String::new()); url.set(String::new());
on_subscribed.call(()); on_subscribed.call(());
} }
Err(err) => status.set(Some(Err(err.to_string()))), SubmitOutcome::Failure { message } => status.set(Some(Err(message))),
} }
submitting.set(false); submitting.set(false);
}); });

View file

@ -10,10 +10,31 @@ pub fn normalize_feed_url(input: &str) -> Option<String> {
} }
/// Success message shown after a feed subscription pulls in new articles. /// Success message shown after a feed subscription pulls in new articles.
pub fn subscribed_message(article_count: usize) -> String { fn subscribed_message(article_count: usize) -> String {
format!("Subscribed — pulled in {article_count} article(s).") format!("Subscribed — pulled in {article_count} article(s).")
} }
/// What the form should show, and whether the URL input should be cleared,
/// after a subscription attempt resolves.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SubmitOutcome {
/// The subscription succeeded: show `message` and clear the input.
Success { message: String },
/// The subscription failed: show `message` and leave the input as-is,
/// so the reader can fix it up and retry.
Failure { message: String },
}
/// Maps a `subscribe_feed` result to what the form should do next.
pub fn resolve_submit_outcome(result: Result<usize, String>) -> SubmitOutcome {
match result {
Ok(count) => SubmitOutcome::Success {
message: subscribed_message(count),
},
Err(message) => SubmitOutcome::Failure { message },
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -33,14 +54,24 @@ mod tests {
} }
#[test] #[test]
fn message_reports_the_article_count() { fn success_reports_the_article_count_and_clears_the_input() {
assert_eq!( assert_eq!(
subscribed_message(0), resolve_submit_outcome(Ok(3)),
"Subscribed — pulled in 0 article(s)." SubmitOutcome::Success {
message: "Subscribed — pulled in 3 article(s).".to_string()
}
); );
}
/// A failed attempt should surface the error but leave the input alone
/// so the reader doesn't have to retype the URL to fix it.
#[test]
fn failure_surfaces_the_error_message() {
assert_eq!( assert_eq!(
subscribed_message(3), resolve_submit_outcome(Err("feed unreachable".to_string())),
"Subscribed — pulled in 3 article(s)." SubmitOutcome::Failure {
message: "feed unreachable".to_string()
}
); );
} }
} }