# ── Structure ───────────────────────────────────────────────────────────────── test_that("calculate_i30 returns correct structure", { data <- data.frame( time = as.POSIXct(c("2024-01-01 10:00", "2024-01-01 10:15", "2024-01-01 10:30", "2024-01-01 10:45")), depth = c(5.2, 8.3, 4.1, 2.5) ) result <- calculate_i30(data, "time", "depth") expect_type(result, "list") expect_named(result, c("i30", "total_rainfall", "duration", "start_time", "end_time", "interval_type")) expect_type(result$i30, "double") expect_true(result$i30 >= 0) }) # ── Equal intervals (auto-detected) ────────────────────────────────────────── test_that("calculate_i30 handles equal intervals correctly (15-min)", { # 4 x 15-min intervals, 5mm each = 20mm total # Best 30-min window = any two consecutive intervals = 10mm/30min = 20 mm/hr data <- data.frame( time = as.POSIXct(c("2024-01-01 10:00", "2024-01-01 10:15", "2024-01-01 10:30", "2024-01-01 10:45")), depth = c(5, 5, 5, 5) ) result <- calculate_i30(data, "time", "depth") expect_equal(result$i30, 20, tolerance = 0.5) expect_equal(result$total_rainfall, 20) expect_equal(result$interval_type, "equal") }) test_that("calculate_i30 handles equal intervals correctly (30-min)", { # 4 x 30-min intervals, 10mm each # Best 30-min window = one interval = 10mm/30min = 20 mm/hr data <- data.frame( time = as.POSIXct(c("2024-01-01 10:00", "2024-01-01 10:30", "2024-01-01 11:00", "2024-01-01 11:30")), depth = c(10, 10, 10, 10) ) result <- calculate_i30(data, "time", "depth") expect_equal(result$i30, 20, tolerance = 0.5) expect_equal(result$total_rainfall, 40) expect_equal(result$interval_type, "equal") }) test_that("calculate_i30 handles equal intervals correctly (5-min)", { # 6 x 5-min intervals — best 30-min window = all 6 = 30mm/30min = 60 mm/hr data <- data.frame( time = as.POSIXct(c("2024-01-01 10:00", "2024-01-01 10:05", "2024-01-01 10:10", "2024-01-01 10:15", "2024-01-01 10:20", "2024-01-01 10:25")), depth = c(5, 5, 5, 5, 5, 5) ) result <- calculate_i30(data, "time", "depth") expect_equal(result$i30, 60, tolerance = 0.5) expect_equal(result$total_rainfall, 30) expect_equal(result$interval_type, "equal") }) # ── Irregular intervals ─────────────────────────────────────────────────────── test_that("calculate_i30 handles irregular intervals correctly", { # Intense burst in first 20 min data <- data.frame( time = as.POSIXct(c("2024-01-01 10:00", "2024-01-01 10:10", "2024-01-01 10:20", "2024-01-01 11:00")), depth = c(2, 15, 13, 1) # 28mm in 20 min ) result <- calculate_i30(data, "time", "depth") # 28mm in 20 min fits in 30-min window → 28/20*60 = 84 mm/hr expect_equal(result$i30, 84, tolerance = 1) expect_equal(result$total_rainfall, 31) expect_equal(result$interval_type, "irregular") }) test_that("calculate_i30 interpolates correctly when interval > 30 min", { # Single 60-min interval with 20mm → interpolated 30-min = 10mm = 20 mm/hr data <- data.frame( time = as.POSIXct(c("2024-01-01 10:00", "2024-01-01 11:00")), depth = c(20, 0) ) expect_warning( result <- calculate_i30(data, "time", "depth"), "Very few data points" ) expect_equal(result$i30, 20, tolerance = 0.5) }) # ── Explicit interval column ────────────────────────────────────────────────── test_that("calculate_i30 works with explicit interval column", { data <- data.frame( time = as.POSIXct(c("2024-01-01 10:00", "2024-01-01 10:20", "2024-01-01 10:40")), depth = c(10, 10, 10), interval = c(20, 20, 20) ) result <- calculate_i30(data, "time", "depth", "interval") # 20mm in 40min → interpolated to 30min = 15mm/30min = 30 mm/hr expect_equal(result$i30, 30, tolerance = 1) expect_equal(result$interval_type, "explicit") }) # ── Total rainfall ──────────────────────────────────────────────────────────── test_that("calculate_i30 calculates total rainfall correctly", { data <- data.frame( time = as.POSIXct(c("2024-01-01 10:00", "2024-01-01 10:15", "2024-01-01 10:30")), depth = c(5.5, 3.2, 1.8) ) result <- calculate_i30(data, "time", "depth") expect_equal(result$total_rainfall, 10.5) }) # ── Single data point ───────────────────────────────────────────────────────── test_that("calculate_i30 handles single data point", { data <- data.frame( time = as.POSIXct("2024-01-01 10:00"), depth = 10 ) # Single row triggers "Very few data points" validation warning expect_warning( result <- calculate_i30(data, "time", "depth"), "Very few data points" ) # 10mm assumed over 30 min = 20 mm/hr expect_equal(result$i30, 20, tolerance = 0.5) expect_equal(result$total_rainfall, 10) }) # ── Invalid data ────────────────────────────────────────────────────────────── test_that("calculate_i30 fails on invalid data", { data <- data.frame( time = as.POSIXct(c("2024-01-01 10:00", "2024-01-01 10:15")), depth = c(5, -2) ) expect_error(calculate_i30(data, "time", "depth"), "Data validation failed") }) # ── Zero rainfall ───────────────────────────────────────────────────────────── test_that("calculate_i30 handles zero rainfall gracefully", { data <- data.frame( time = as.POSIXct(c("2024-01-01 10:00", "2024-01-01 10:15", "2024-01-01 10:30")), depth = c(0, 0, 0) ) # All-zero depths triggers validation warning expect_warning( result <- calculate_i30(data, "time", "depth"), "All rainfall depths are zero" ) expect_equal(result$i30, 0) expect_equal(result$total_rainfall, 0) }) # ── Built-in dataset ────────────────────────────────────────────────────────── test_that("calculate_i30 works with built-in rainfall_single dataset", { data("rainfall_single", package = "rainerosr") result <- calculate_i30(rainfall_single, "datetime", "rainfall_mm") expect_type(result, "list") expect_true(result$i30 > 0) expect_equal(result$total_rainfall, sum(rainfall_single$rainfall_mm)) })