#![forbid(unsafe_code)]
use super::Breakpoint;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Responsive<T> {
values: [Option<T>; 5],
}
impl<T: Clone> Responsive<T> {
#[must_use]
pub fn new(base: T) -> Self {
Self {
values: [Some(base), None, None, None, None],
}
}
#[must_use]
pub fn at(mut self, bp: Breakpoint, value: T) -> Self {
self.values[bp as usize] = Some(value);
self
}
pub fn set(&mut self, bp: Breakpoint, value: T) {
self.values[bp as usize] = Some(value);
}
pub fn clear(&mut self, bp: Breakpoint) {
if bp != Breakpoint::Xs {
self.values[bp as usize] = None;
}
}
#[must_use]
pub fn resolve(&self, bp: Breakpoint) -> &T {
let idx = bp as usize;
for i in (0..=idx).rev() {
if let Some(ref v) = self.values[i] {
return v;
}
}
self.values[0].as_ref().expect("Xs always has a value")
}
#[must_use]
pub fn resolve_cloned(&self, bp: Breakpoint) -> T {
self.resolve(bp).clone()
}
#[must_use]
pub fn has_explicit(&self, bp: Breakpoint) -> bool {
self.values[bp as usize].is_some()
}
pub fn explicit_values(&self) -> impl Iterator<Item = (Breakpoint, &T)> {
Breakpoint::ALL
.iter()
.zip(self.values.iter())
.filter_map(|(&bp, v)| v.as_ref().map(|val| (bp, val)))
}
#[must_use]
pub fn map<U: Clone>(&self, f: impl Fn(&T) -> U) -> Responsive<U> {
Responsive {
values: [
self.values[0].as_ref().map(&f),
self.values[1].as_ref().map(&f),
self.values[2].as_ref().map(&f),
self.values[3].as_ref().map(&f),
self.values[4].as_ref().map(&f),
],
}
}
}
impl<T: Clone + Default> Default for Responsive<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: Clone + std::fmt::Display> std::fmt::Display for Responsive<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Responsive(")?;
let mut first = true;
for (bp, val) in self.explicit_values() {
if !first {
write!(f, ", ")?;
}
write!(f, "{}={}", bp, val)?;
first = false;
}
write!(f, ")")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn base_value_at_all_breakpoints() {
let r = Responsive::new(42);
for bp in Breakpoint::ALL {
assert_eq!(r.resolve(bp), &42);
}
}
#[test]
fn override_single_breakpoint() {
let r = Responsive::new(1).at(Breakpoint::Md, 2);
assert_eq!(r.resolve(Breakpoint::Xs), &1);
assert_eq!(r.resolve(Breakpoint::Sm), &1); assert_eq!(r.resolve(Breakpoint::Md), &2); assert_eq!(r.resolve(Breakpoint::Lg), &2); assert_eq!(r.resolve(Breakpoint::Xl), &2); }
#[test]
fn override_multiple_breakpoints() {
let r = Responsive::new(0)
.at(Breakpoint::Sm, 1)
.at(Breakpoint::Lg, 3);
assert_eq!(r.resolve(Breakpoint::Xs), &0);
assert_eq!(r.resolve(Breakpoint::Sm), &1);
assert_eq!(r.resolve(Breakpoint::Md), &1); assert_eq!(r.resolve(Breakpoint::Lg), &3);
assert_eq!(r.resolve(Breakpoint::Xl), &3); }
#[test]
fn set_mutating() {
let mut r = Responsive::new(0);
r.set(Breakpoint::Xl, 5);
assert_eq!(r.resolve(Breakpoint::Xl), &5);
}
#[test]
fn clear_reverts_to_inheritance() {
let mut r = Responsive::new(1).at(Breakpoint::Md, 2);
assert_eq!(r.resolve(Breakpoint::Md), &2);
r.clear(Breakpoint::Md);
assert_eq!(r.resolve(Breakpoint::Md), &1); }
#[test]
fn clear_xs_is_noop() {
let mut r = Responsive::new(42);
r.clear(Breakpoint::Xs);
assert_eq!(r.resolve(Breakpoint::Xs), &42);
}
#[test]
fn has_explicit() {
let r = Responsive::new(0).at(Breakpoint::Lg, 3);
assert!(r.has_explicit(Breakpoint::Xs));
assert!(!r.has_explicit(Breakpoint::Sm));
assert!(!r.has_explicit(Breakpoint::Md));
assert!(r.has_explicit(Breakpoint::Lg));
assert!(!r.has_explicit(Breakpoint::Xl));
}
#[test]
fn explicit_values_iterator() {
let r = Responsive::new(0)
.at(Breakpoint::Md, 2)
.at(Breakpoint::Xl, 4);
let explicit: Vec<_> = r.explicit_values().collect();
assert_eq!(explicit.len(), 3);
assert_eq!(explicit[0], (Breakpoint::Xs, &0));
assert_eq!(explicit[1], (Breakpoint::Md, &2));
assert_eq!(explicit[2], (Breakpoint::Xl, &4));
}
#[test]
fn map_values() {
let r = Responsive::new(10).at(Breakpoint::Lg, 20);
let doubled = r.map(|v| v * 2);
assert_eq!(doubled.resolve(Breakpoint::Xs), &20);
assert_eq!(doubled.resolve(Breakpoint::Lg), &40);
}
#[test]
fn resolve_cloned() {
let r = Responsive::new("hello".to_string());
let val: String = r.resolve_cloned(Breakpoint::Md);
assert_eq!(val, "hello");
}
#[test]
fn default() {
let r: Responsive<i32> = Responsive::default();
assert_eq!(r.resolve(Breakpoint::Xs), &0);
}
#[test]
fn clone_independence() {
let r1 = Responsive::new(1);
let mut r2 = r1.clone();
r2.set(Breakpoint::Md, 99);
assert_eq!(r1.resolve(Breakpoint::Md), &1);
assert_eq!(r2.resolve(Breakpoint::Md), &99);
}
#[test]
fn display_format() {
let r = Responsive::new(0).at(Breakpoint::Md, 2);
let s = format!("{}", r);
assert!(s.contains("xs=0"));
assert!(s.contains("md=2"));
}
#[test]
fn string_responsive() {
let r = Responsive::new("compact".to_string())
.at(Breakpoint::Md, "standard".to_string())
.at(Breakpoint::Xl, "expanded".to_string());
assert_eq!(r.resolve(Breakpoint::Xs), "compact");
assert_eq!(r.resolve(Breakpoint::Sm), "compact");
assert_eq!(r.resolve(Breakpoint::Md), "standard");
assert_eq!(r.resolve(Breakpoint::Lg), "standard");
assert_eq!(r.resolve(Breakpoint::Xl), "expanded");
}
#[test]
fn all_breakpoints_overridden() {
let r = Responsive::new(0)
.at(Breakpoint::Sm, 1)
.at(Breakpoint::Md, 2)
.at(Breakpoint::Lg, 3)
.at(Breakpoint::Xl, 4);
assert_eq!(r.resolve(Breakpoint::Xs), &0);
assert_eq!(r.resolve(Breakpoint::Sm), &1);
assert_eq!(r.resolve(Breakpoint::Md), &2);
assert_eq!(r.resolve(Breakpoint::Lg), &3);
assert_eq!(r.resolve(Breakpoint::Xl), &4);
}
#[test]
fn equality() {
let r1 = Responsive::new(1).at(Breakpoint::Md, 2);
let r2 = Responsive::new(1).at(Breakpoint::Md, 2);
assert_eq!(r1, r2);
}
}