use super::signal::{RwSignal, new_rw_signal};
use crate::secret::Secret;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Password {
inner: RwSignal<Secret>,
}
pub fn create_password() -> Password {
Password {
inner: new_rw_signal(Secret::new()),
}
}
impl Password {
#[cfg_attr(debug_assertions, track_caller)]
pub fn with<R>(&self, f: impl FnOnce(&Secret) -> R) -> R {
self.inner.with(f)
}
pub fn with_untracked<R>(&self, f: impl FnOnce(&Secret) -> R) -> R {
self.inner.with_untracked(f)
}
#[cfg_attr(debug_assertions, track_caller)]
pub fn is_empty(&self) -> bool {
self.with(Secret::is_empty)
}
pub fn set(&self, secret: Secret) {
self.inner.update_always(|held| *held = secret);
}
pub fn clear(&self) {
if !self.with_untracked(Secret::is_empty) {
self.inner.update_always(Secret::clear);
}
}
pub(crate) fn edit(&self, f: impl FnOnce(&mut Secret)) {
self.inner.update_always(f);
}
pub fn take(&self) -> Secret {
let mut taken = Secret::new();
self.inner
.update_always(|held| std::mem::swap(held, &mut taken));
taken
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reactive::create_effect;
use std::cell::Cell;
use std::rc::Rc;
#[test]
fn a_reader_follows_it_filling_and_emptying() {
let password = create_password();
let seen = Rc::new(Cell::new(None));
let runs = Rc::new(Cell::new(0));
let (s, r) = (seen.clone(), runs.clone());
create_effect(move || {
s.set(Some(password.is_empty()));
r.set(r.get() + 1);
});
assert_eq!(seen.get(), Some(true));
password.set(Secret::from(String::from("hunter2")));
assert_eq!(seen.get(), Some(false), "set did not notify");
password.clear();
assert_eq!(seen.get(), Some(true), "clear did not notify");
let before = runs.get();
password.clear();
assert_eq!(runs.get(), before, "clearing an empty password notified");
}
#[test]
fn take_leaves_it_empty_and_hands_the_text_over() {
let password = create_password();
password.edit(|s| s.replace_range(0..0, "hunter2"));
let taken = password.take();
assert_eq!(taken.expose(), "hunter2");
assert!(password.with_untracked(Secret::is_empty));
}
}