#![cfg(wasm)]
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
#[allow(deprecated)]
pub use reinhardt_pages::testing::{
MockResponse, assert_server_fn_call_count, assert_server_fn_called,
assert_server_fn_called_with, assert_server_fn_not_called, clear_mocks, get_call_history,
mock_server_fn, mock_server_fn_error,
};
#[derive(Debug, Clone, Default)]
pub struct MockStorage {
data: Rc<RefCell<HashMap<String, String>>>,
}
impl MockStorage {
pub fn new() -> Self {
Self::default()
}
pub fn with_data(data: HashMap<String, String>) -> Self {
Self {
data: Rc::new(RefCell::new(data)),
}
}
pub fn length(&self) -> usize {
self.data.borrow().len()
}
pub fn get_item(&self, key: &str) -> Option<String> {
self.data.borrow().get(key).cloned()
}
pub fn set_item(&self, key: &str, value: &str) {
self.data
.borrow_mut()
.insert(key.to_string(), value.to_string());
}
pub fn remove_item(&self, key: &str) {
self.data.borrow_mut().remove(key);
}
pub fn clear(&self) {
self.data.borrow_mut().clear();
}
pub fn key(&self, index: usize) -> Option<String> {
self.data.borrow().keys().nth(index).cloned()
}
pub fn keys(&self) -> Vec<String> {
self.data.borrow().keys().cloned().collect()
}
pub fn values(&self) -> Vec<String> {
self.data.borrow().values().cloned().collect()
}
pub fn entries(&self) -> Vec<(String, String)> {
self.data
.borrow()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
pub fn contains_key(&self, key: &str) -> bool {
self.data.borrow().contains_key(key)
}
}
#[derive(Debug, Clone, Default)]
pub struct MockCookies {
cookies: Rc<RefCell<HashMap<String, CookieEntry>>>,
}
#[derive(Debug, Clone)]
pub struct CookieEntry {
pub value: String,
pub options: CookieOptions,
}
#[derive(Debug, Clone, Default)]
pub struct CookieOptions {
pub max_age: Option<i64>,
pub expires: Option<i64>,
pub path: Option<String>,
pub domain: Option<String>,
pub secure: bool,
pub http_only: bool,
pub same_site: Option<SameSite>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SameSite {
Strict,
Lax,
None,
}
impl MockCookies {
pub fn new() -> Self {
Self::default()
}
pub fn with_cookies(cookies: HashMap<String, String>) -> Self {
let entries: HashMap<String, CookieEntry> = cookies
.into_iter()
.map(|(k, v)| {
(
k,
CookieEntry {
value: v,
options: CookieOptions::default(),
},
)
})
.collect();
Self {
cookies: Rc::new(RefCell::new(entries)),
}
}
pub fn get(&self, name: &str) -> Option<String> {
self.cookies
.borrow()
.get(name)
.map(|entry| entry.value.clone())
}
pub fn get_entry(&self, name: &str) -> Option<CookieEntry> {
self.cookies.borrow().get(name).cloned()
}
pub fn set(&self, name: &str, value: &str) {
self.set_with_options(name, value, CookieOptions::default());
}
pub fn set_with_options(&self, name: &str, value: &str, options: CookieOptions) {
self.cookies.borrow_mut().insert(
name.to_string(),
CookieEntry {
value: value.to_string(),
options,
},
);
}
pub fn remove(&self, name: &str) {
self.cookies.borrow_mut().remove(name);
}
pub fn clear(&self) {
self.cookies.borrow_mut().clear();
}
pub fn names(&self) -> Vec<String> {
self.cookies.borrow().keys().cloned().collect()
}
pub fn all(&self) -> HashMap<String, String> {
self.cookies
.borrow()
.iter()
.map(|(k, v)| (k.clone(), v.value.clone()))
.collect()
}
pub fn has(&self, name: &str) -> bool {
self.cookies.borrow().contains_key(name)
}
pub fn len(&self) -> usize {
self.cookies.borrow().len()
}
pub fn is_empty(&self) -> bool {
self.cookies.borrow().is_empty()
}
pub fn to_cookie_string(&self) -> String {
self.cookies
.borrow()
.iter()
.map(|(name, entry)| format!("{}={}", name, entry.value))
.collect::<Vec<_>>()
.join("; ")
}
}
#[deprecated(
since = "0.1.0-rc.16",
note = "Use `MockServiceWorker` from `reinhardt_test::msw` instead. See issue #3283."
)]
#[derive(Debug, Clone, Default)]
pub struct MockFetch {
responses: Rc<RefCell<HashMap<String, MockFetchResponse>>>,
calls: Rc<RefCell<Vec<MockFetchCall>>>,
}
#[derive(Debug, Clone)]
pub struct MockFetchCall {
pub url: String,
pub method: String,
pub headers: HashMap<String, String>,
pub body: Option<String>,
}
#[derive(Debug, Clone)]
pub struct MockFetchResponse {
pub status: u16,
pub status_text: String,
pub headers: HashMap<String, String>,
pub body: String,
pub network_error: bool,
}
impl MockFetchResponse {
pub fn json<T: serde::Serialize>(data: T) -> Self {
let body = serde_json::to_string(&data).unwrap_or_default();
Self {
status: 200,
status_text: "OK".to_string(),
headers: [("content-type".to_string(), "application/json".to_string())]
.into_iter()
.collect(),
body,
network_error: false,
}
}
pub fn text(body: impl Into<String>) -> Self {
Self {
status: 200,
status_text: "OK".to_string(),
headers: [("content-type".to_string(), "text/plain".to_string())]
.into_iter()
.collect(),
body: body.into(),
network_error: false,
}
}
pub fn error(status: u16, message: impl Into<String>) -> Self {
Self {
status,
status_text: http_status_text(status).to_string(),
headers: HashMap::new(),
body: message.into(),
network_error: false,
}
}
pub fn network_error() -> Self {
Self {
status: 0,
status_text: String::new(),
headers: HashMap::new(),
body: String::new(),
network_error: true,
}
}
pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(name.into(), value.into());
self
}
pub fn with_status(mut self, status: u16) -> Self {
self.status = status;
self.status_text = http_status_text(status).to_string();
self
}
}
impl MockFetch {
pub fn new() -> Self {
Self::default()
}
pub fn mock_response(&self, url: impl Into<String>, response: MockFetchResponse) {
self.responses.borrow_mut().insert(url.into(), response);
}
pub fn remove_mock(&self, url: &str) {
self.responses.borrow_mut().remove(url);
}
pub fn clear_mocks(&self) {
self.responses.borrow_mut().clear();
}
pub fn calls(&self) -> Vec<MockFetchCall> {
self.calls.borrow().clone()
}
pub fn calls_to(&self, url: &str) -> Vec<MockFetchCall> {
self.calls
.borrow()
.iter()
.filter(|call| call.url == url)
.cloned()
.collect()
}
pub fn clear_calls(&self) {
self.calls.borrow_mut().clear();
}
pub fn was_called(&self, url: &str) -> bool {
self.calls.borrow().iter().any(|call| call.url == url)
}
pub fn call_count(&self, url: &str) -> usize {
self.calls
.borrow()
.iter()
.filter(|call| call.url == url)
.count()
}
pub fn record_call(&self, call: MockFetchCall) {
self.calls.borrow_mut().push(call);
}
pub fn get_response(&self, url: &str) -> Option<MockFetchResponse> {
self.responses.borrow().get(url).cloned()
}
}
fn http_status_text(status: u16) -> &'static str {
match status {
200 => "OK",
201 => "Created",
204 => "No Content",
400 => "Bad Request",
401 => "Unauthorized",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
409 => "Conflict",
422 => "Unprocessable Entity",
429 => "Too Many Requests",
500 => "Internal Server Error",
502 => "Bad Gateway",
503 => "Service Unavailable",
_ => "Unknown",
}
}
#[derive(Debug, Default)]
pub struct MockTimers {
callbacks: Rc<RefCell<Vec<TimerCallback>>>,
current_time: Rc<RefCell<f64>>,
}
struct TimerCallback {
id: u32,
callback: Box<dyn FnOnce()>,
scheduled_time: f64,
is_interval: bool,
interval_ms: Option<u32>,
}
impl std::fmt::Debug for TimerCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TimerCallback")
.field("id", &self.id)
.field("callback", &"<FnOnce>")
.field("scheduled_time", &self.scheduled_time)
.field("is_interval", &self.is_interval)
.field("interval_ms", &self.interval_ms)
.finish()
}
}
impl MockTimers {
pub fn new() -> Self {
Self::default()
}
pub fn now(&self) -> f64 {
*self.current_time.borrow()
}
pub fn advance_by(&self, ms: u32) {
let new_time = *self.current_time.borrow() + ms as f64;
*self.current_time.borrow_mut() = new_time;
self.run_due_callbacks();
}
pub fn run_all(&self) {
let max_time = self
.callbacks
.borrow()
.iter()
.map(|cb| cb.scheduled_time)
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(0.0);
if max_time > *self.current_time.borrow() {
*self.current_time.borrow_mut() = max_time;
self.run_due_callbacks();
}
}
pub fn pending_count(&self) -> usize {
self.callbacks.borrow().len()
}
pub fn clear_all(&self) {
self.callbacks.borrow_mut().clear();
}
fn run_due_callbacks(&self) {
let current = *self.current_time.borrow();
let all_callbacks: Vec<TimerCallback> = self.callbacks.borrow_mut().drain(..).collect();
let mut remaining = Vec::new();
let mut due = Vec::new();
for cb in all_callbacks {
if cb.scheduled_time <= current {
due.push(cb);
} else {
remaining.push(cb);
}
}
*self.callbacks.borrow_mut() = remaining;
due.sort_by(|a, b| {
a.scheduled_time
.partial_cmp(&b.scheduled_time)
.unwrap_or(std::cmp::Ordering::Equal)
});
for cb in due {
(cb.callback)();
}
}
}
#[derive(Debug)]
pub struct MutationTracker {
mutations: Rc<RefCell<Vec<MutationRecord>>>,
}
#[derive(Debug, Clone)]
pub struct MutationRecord {
pub mutation_type: String,
pub target: String,
pub attribute_name: Option<String>,
pub old_value: Option<String>,
pub added_nodes_count: usize,
pub removed_nodes_count: usize,
}
impl MutationTracker {
pub fn new(_element: &web_sys::Element) -> Self {
unimplemented!(
"MutationTracker requires a WASM runtime with browser DOM access. \
MutationObserver cannot be set up outside of an actual browser environment."
)
}
pub fn mutations(&self) -> Vec<MutationRecord> {
self.mutations.borrow().clone()
}
pub fn clear(&self) {
self.mutations.borrow_mut().clear();
}
pub fn has_mutations(&self) -> bool {
!self.mutations.borrow().is_empty()
}
pub fn mutation_count(&self) -> usize {
self.mutations.borrow().len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
fn test_mock_storage_operations() {
let storage = MockStorage::new();
assert_eq!(storage.length(), 0);
storage.set_item("key1", "value1");
storage.set_item("key2", "value2");
assert_eq!(storage.length(), 2);
assert_eq!(storage.get_item("key1"), Some("value1".to_string()));
assert_eq!(storage.get_item("key2"), Some("value2".to_string()));
assert_eq!(storage.get_item("key3"), None);
storage.remove_item("key1");
assert_eq!(storage.length(), 1);
assert_eq!(storage.get_item("key1"), None);
storage.clear();
assert_eq!(storage.length(), 0);
}
#[rstest]
fn test_mock_storage_with_data() {
let mut data = HashMap::new();
data.insert("a".to_string(), "1".to_string());
data.insert("b".to_string(), "2".to_string());
let storage = MockStorage::with_data(data);
assert_eq!(storage.length(), 2);
assert_eq!(storage.get_item("a"), Some("1".to_string()));
assert_eq!(storage.get_item("b"), Some("2".to_string()));
}
#[rstest]
fn test_mock_storage_key_by_index() {
let storage = MockStorage::new();
storage.set_item("alpha", "val");
let key_0 = storage.key(0);
let key_out_of_bounds = storage.key(99);
assert_eq!(key_0, Some("alpha".to_string()));
assert_eq!(key_out_of_bounds, None);
}
#[rstest]
fn test_mock_storage_keys_values_entries() {
let storage = MockStorage::new();
storage.set_item("x", "10");
storage.set_item("y", "20");
let keys = storage.keys();
let values = storage.values();
let entries = storage.entries();
assert_eq!(keys.len(), 2);
assert!(keys.contains(&"x".to_string()));
assert!(keys.contains(&"y".to_string()));
assert_eq!(values.len(), 2);
assert!(values.contains(&"10".to_string()));
assert!(values.contains(&"20".to_string()));
assert_eq!(entries.len(), 2);
}
#[rstest]
fn test_mock_storage_contains_key() {
let storage = MockStorage::new();
storage.set_item("present", "yes");
assert!(storage.contains_key("present"));
assert!(!storage.contains_key("absent"));
}
#[rstest]
fn test_mock_storage_length_after_ops() {
let storage = MockStorage::new();
assert_eq!(storage.length(), 0);
storage.set_item("a", "1");
assert_eq!(storage.length(), 1);
storage.set_item("b", "2");
assert_eq!(storage.length(), 2);
storage.remove_item("a");
assert_eq!(storage.length(), 1);
storage.clear();
assert_eq!(storage.length(), 0);
}
#[rstest]
fn test_mock_storage_overwrite() {
let storage = MockStorage::new();
storage.set_item("key", "old_value");
storage.set_item("key", "new_value");
assert_eq!(storage.length(), 1);
assert_eq!(storage.get_item("key"), Some("new_value".to_string()));
}
#[rstest]
fn test_mock_storage_remove_nonexistent() {
let storage = MockStorage::new();
storage.set_item("key", "value");
storage.remove_item("nonexistent");
assert_eq!(storage.length(), 1);
assert_eq!(storage.get_item("key"), Some("value".to_string()));
}
#[rstest]
fn test_mock_storage_empty_key_and_value() {
let storage = MockStorage::new();
storage.set_item("", "empty_key");
storage.set_item("empty_value", "");
assert_eq!(storage.get_item(""), Some("empty_key".to_string()));
assert_eq!(storage.get_item("empty_value"), Some(String::new()));
assert_eq!(storage.length(), 2);
}
#[rstest]
fn test_mock_storage_clone_independence() {
let storage = MockStorage::new();
storage.set_item("shared", "data");
let cloned = storage.clone();
cloned.set_item("extra", "value");
assert_eq!(storage.length(), 2);
assert_eq!(storage.get_item("extra"), Some("value".to_string()));
}
#[rstest]
fn test_mock_cookies_operations() {
let cookies = MockCookies::new();
assert!(cookies.is_empty());
cookies.set("session", "abc123");
cookies.set_with_options(
"auth",
"xyz",
CookieOptions {
secure: true,
http_only: true,
..Default::default()
},
);
assert_eq!(cookies.len(), 2);
assert_eq!(cookies.get("session"), Some("abc123".to_string()));
assert_eq!(cookies.get("auth"), Some("xyz".to_string()));
let entry = cookies.get_entry("auth").unwrap();
assert!(entry.options.secure);
assert!(entry.options.http_only);
cookies.remove("session");
assert!(!cookies.has("session"));
assert!(cookies.has("auth"));
}
#[rstest]
fn test_mock_cookies_with_cookies_constructor() {
let mut initial = HashMap::new();
initial.insert("a".to_string(), "1".to_string());
initial.insert("b".to_string(), "2".to_string());
let cookies = MockCookies::with_cookies(initial);
assert_eq!(cookies.len(), 2);
assert_eq!(cookies.get("a"), Some("1".to_string()));
assert_eq!(cookies.get("b"), Some("2".to_string()));
}
#[rstest]
fn test_mock_cookies_names_and_all() {
let cookies = MockCookies::new();
cookies.set("x", "10");
cookies.set("y", "20");
let names = cookies.names();
let all = cookies.all();
assert_eq!(names.len(), 2);
assert!(names.contains(&"x".to_string()));
assert!(names.contains(&"y".to_string()));
assert_eq!(all.len(), 2);
assert_eq!(all.get("x"), Some(&"10".to_string()));
assert_eq!(all.get("y"), Some(&"20".to_string()));
}
#[rstest]
fn test_mock_cookies_is_empty_transitions() {
let cookies = MockCookies::new();
assert!(cookies.is_empty());
cookies.set("k", "v");
assert!(!cookies.is_empty());
cookies.remove("k");
assert!(cookies.is_empty());
}
#[rstest]
fn test_mock_cookies_get_entry_with_options() {
let cookies = MockCookies::new();
cookies.set_with_options(
"tracking",
"abc",
CookieOptions {
max_age: Some(3600),
path: Some("/app".to_string()),
domain: Some("example.com".to_string()),
secure: true,
http_only: false,
same_site: Some(SameSite::Lax),
expires: None,
},
);
let entry = cookies.get_entry("tracking").unwrap();
assert_eq!(entry.value, "abc");
assert_eq!(entry.options.max_age, Some(3600));
assert_eq!(entry.options.path, Some("/app".to_string()));
assert_eq!(entry.options.domain, Some("example.com".to_string()));
assert!(entry.options.secure);
assert!(!entry.options.http_only);
assert_eq!(entry.options.same_site, Some(SameSite::Lax));
}
#[rstest]
fn test_mock_cookies_get_entry_nonexistent() {
let cookies = MockCookies::new();
let entry = cookies.get_entry("missing");
assert!(entry.is_none());
}
#[rstest]
fn test_mock_cookies_to_cookie_string_format() {
let mut initial = HashMap::new();
initial.insert("a".to_string(), "1".to_string());
let cookies = MockCookies::with_cookies(initial);
let cookie_str = cookies.to_cookie_string();
assert_eq!(cookie_str, "a=1");
}
#[rstest]
fn test_mock_cookies_to_cookie_string_empty() {
let cookies = MockCookies::new();
let cookie_str = cookies.to_cookie_string();
assert_eq!(cookie_str, "");
}
#[rstest]
fn test_mock_cookies_to_cookie_string_multiple() {
let cookies = MockCookies::new();
cookies.set("a", "1");
cookies.set("b", "2");
let cookie_str = cookies.to_cookie_string();
assert!(cookie_str.contains("a=1"));
assert!(cookie_str.contains("b=2"));
assert!(cookie_str.contains("; "));
}
#[rstest]
fn test_cookie_options_default() {
let opts = CookieOptions::default();
assert_eq!(opts.max_age, None);
assert_eq!(opts.expires, None);
assert_eq!(opts.path, None);
assert_eq!(opts.domain, None);
assert!(!opts.secure);
assert!(!opts.http_only);
assert!(opts.same_site.is_none());
}
#[rstest]
fn test_same_site_variants() {
assert_eq!(SameSite::Strict, SameSite::Strict);
assert_eq!(SameSite::Lax, SameSite::Lax);
assert_eq!(SameSite::None, SameSite::None);
assert_ne!(SameSite::Strict, SameSite::Lax);
assert_ne!(SameSite::Lax, SameSite::None);
assert_ne!(SameSite::Strict, SameSite::None);
}
#[rstest]
fn test_mock_cookies_clear() {
let cookies = MockCookies::new();
cookies.set("a", "1");
cookies.set("b", "2");
assert_eq!(cookies.len(), 2);
cookies.clear();
assert_eq!(cookies.len(), 0);
assert!(cookies.is_empty());
}
#[rstest]
fn test_mock_fetch_response_builders() {
let json_response = MockFetchResponse::json(serde_json::json!({"status": "ok"}));
assert_eq!(json_response.status, 200);
assert!(json_response.body.contains("status"));
let text_response = MockFetchResponse::text("Hello, World!");
assert_eq!(text_response.body, "Hello, World!");
let error_response = MockFetchResponse::error(404, "Not found");
assert_eq!(error_response.status, 404);
assert_eq!(error_response.status_text, "Not Found");
let network_error = MockFetchResponse::network_error();
assert!(network_error.network_error);
}
#[rstest]
fn test_mock_fetch_recording() {
let fetch = MockFetch::new();
fetch.record_call(MockFetchCall {
url: "/api/users".to_string(),
method: "GET".to_string(),
headers: HashMap::new(),
body: None,
});
fetch.record_call(MockFetchCall {
url: "/api/posts".to_string(),
method: "POST".to_string(),
headers: HashMap::new(),
body: Some(r#"{"title":"Test"}"#.to_string()),
});
assert!(fetch.was_called("/api/users"));
assert!(fetch.was_called("/api/posts"));
assert!(!fetch.was_called("/api/comments"));
assert_eq!(fetch.call_count("/api/users"), 1);
assert_eq!(fetch.calls().len(), 2);
}
#[rstest]
fn test_mock_fetch_get_response_matching() {
let fetch = MockFetch::new();
let response = MockFetchResponse::json(serde_json::json!({"id": 1}));
fetch.mock_response("/api/data", response);
let result = fetch.get_response("/api/data");
assert!(result.is_some());
let resp = result.unwrap();
assert_eq!(resp.status, 200);
assert!(resp.body.contains("id"));
}
#[rstest]
fn test_mock_fetch_get_response_unregistered_returns_none() {
let fetch = MockFetch::new();
let result = fetch.get_response("/unknown");
assert!(result.is_none());
}
#[rstest]
fn test_mock_fetch_remove_mock() {
let fetch = MockFetch::new();
fetch.mock_response("/api/x", MockFetchResponse::text("hello"));
assert!(fetch.get_response("/api/x").is_some());
fetch.remove_mock("/api/x");
assert!(fetch.get_response("/api/x").is_none());
}
#[rstest]
fn test_mock_fetch_clear_mocks() {
let fetch = MockFetch::new();
fetch.mock_response("/a", MockFetchResponse::text("1"));
fetch.mock_response("/b", MockFetchResponse::text("2"));
fetch.clear_mocks();
assert!(fetch.get_response("/a").is_none());
assert!(fetch.get_response("/b").is_none());
}
#[rstest]
fn test_mock_fetch_calls_to_filtering() {
let fetch = MockFetch::new();
fetch.record_call(MockFetchCall {
url: "/api/a".to_string(),
method: "GET".to_string(),
headers: HashMap::new(),
body: None,
});
fetch.record_call(MockFetchCall {
url: "/api/b".to_string(),
method: "GET".to_string(),
headers: HashMap::new(),
body: None,
});
fetch.record_call(MockFetchCall {
url: "/api/a".to_string(),
method: "POST".to_string(),
headers: HashMap::new(),
body: Some("data".to_string()),
});
let calls_a = fetch.calls_to("/api/a");
let calls_b = fetch.calls_to("/api/b");
let calls_c = fetch.calls_to("/api/c");
assert_eq!(calls_a.len(), 2);
assert_eq!(calls_b.len(), 1);
assert_eq!(calls_c.len(), 0);
}
#[rstest]
fn test_mock_fetch_clear_calls() {
let fetch = MockFetch::new();
fetch.record_call(MockFetchCall {
url: "/api/x".to_string(),
method: "GET".to_string(),
headers: HashMap::new(),
body: None,
});
assert_eq!(fetch.calls().len(), 1);
fetch.clear_calls();
assert_eq!(fetch.calls().len(), 0);
assert!(!fetch.was_called("/api/x"));
}
#[rstest]
fn test_mock_fetch_call_count_multiple() {
let fetch = MockFetch::new();
for _ in 0..5 {
fetch.record_call(MockFetchCall {
url: "/api/repeat".to_string(),
method: "GET".to_string(),
headers: HashMap::new(),
body: None,
});
}
fetch.record_call(MockFetchCall {
url: "/api/other".to_string(),
method: "GET".to_string(),
headers: HashMap::new(),
body: None,
});
assert_eq!(fetch.call_count("/api/repeat"), 5);
assert_eq!(fetch.call_count("/api/other"), 1);
assert_eq!(fetch.call_count("/api/none"), 0);
}
#[rstest]
fn test_mock_fetch_response_json_content_type() {
let resp = MockFetchResponse::json(serde_json::json!([1, 2, 3]));
assert_eq!(
resp.headers.get("content-type"),
Some(&"application/json".to_string())
);
assert_eq!(resp.status, 200);
assert_eq!(resp.status_text, "OK");
assert!(!resp.network_error);
}
#[rstest]
fn test_mock_fetch_response_text_content_type() {
let resp = MockFetchResponse::text("plain text");
assert_eq!(
resp.headers.get("content-type"),
Some(&"text/plain".to_string())
);
assert_eq!(resp.body, "plain text");
assert_eq!(resp.status, 200);
assert!(!resp.network_error);
}
#[rstest]
fn test_mock_fetch_response_with_status() {
let resp = MockFetchResponse::text("created");
let resp = resp.with_status(201);
assert_eq!(resp.status, 201);
assert_eq!(resp.status_text, "Created");
}
#[rstest]
fn test_mock_fetch_response_with_header() {
let resp = MockFetchResponse::text("body");
let resp = resp
.with_header("X-Custom", "value1")
.with_header("X-Another", "value2");
assert_eq!(resp.headers.get("X-Custom"), Some(&"value1".to_string()));
assert_eq!(resp.headers.get("X-Another"), Some(&"value2".to_string()));
assert_eq!(
resp.headers.get("content-type"),
Some(&"text/plain".to_string())
);
}
#[rstest]
fn test_mock_fetch_response_error_various_statuses() {
let resp_400 = MockFetchResponse::error(400, "bad");
assert_eq!(resp_400.status, 400);
assert_eq!(resp_400.status_text, "Bad Request");
let resp_500 = MockFetchResponse::error(500, "fail");
assert_eq!(resp_500.status, 500);
assert_eq!(resp_500.status_text, "Internal Server Error");
let resp_unknown = MockFetchResponse::error(999, "mystery");
assert_eq!(resp_unknown.status, 999);
assert_eq!(resp_unknown.status_text, "Unknown");
}
#[rstest]
fn test_mock_fetch_response_network_error_fields() {
let resp = MockFetchResponse::network_error();
assert!(resp.network_error);
assert_eq!(resp.status, 0);
assert_eq!(resp.status_text, "");
assert!(resp.headers.is_empty());
assert_eq!(resp.body, "");
}
#[rstest]
fn test_mock_timers_initial_state() {
let timers = MockTimers::new();
assert_eq!(timers.now(), 0.0);
assert_eq!(timers.pending_count(), 0);
}
#[rstest]
fn test_mock_timers_advance_by() {
let timers = MockTimers::new();
timers.advance_by(100);
assert_eq!(timers.now(), 100.0);
timers.advance_by(50);
assert_eq!(timers.now(), 150.0);
}
#[rstest]
fn test_mock_timers_clear_all() {
let timers = MockTimers::new();
timers.clear_all();
assert_eq!(timers.pending_count(), 0);
}
}