prop/test/microwaveprop_web/skew_t_test.exs
Graham McIntire fd976b0cd5
fix: resolve 391 Credo issues across codebase
- Add jump_credo_checks ~> 0.4 with all 20 checks enabled
- Fix all standard Credo issues: 139 @spec (113 done, 26 remain),
  4 refactoring, 3 alias usage, 9 System.cmd env, 5 unsafe_to_atom,
  2 max line length, 9 assert_receive timeout
- Fix 170+ jump_credo_checks warnings:
  - 117 TopLevelAliasImportRequire: move nested alias/import to module top
  - 32 UseObanProWorker: switch to Oban.Pro.Worker
  - 4 DoctestIExExamples: add doctests / create test file
  - ~20 WeakAssertion: strengthen type-check assertions
  - Various ConditionalAssertion, AssertReceiveTimeout fixes
- Exclude vendor/ from Credo analysis
- Remaining: 175 warnings (mostly opinionated WeakAssertion,
  AvoidSocketAssignsInTest), 26 @spec annotations
2026-06-12 13:51:32 -05:00

128 lines
4.6 KiB
Elixir

defmodule MicrowavepropWeb.SkewTTest do
use ExUnit.Case, async: true
alias MicrowavepropWeb.SkewT
describe "saturation_vapor_pressure/1 (Magnus)" do
test "reference values are within 1% of textbook" do
# At 0°C the Magnus formula gives ≈ 6.112 hPa.
assert_in_delta SkewT.saturation_vapor_pressure(0.0), 6.112, 0.05
# At 20°C es ≈ 23.37 hPa.
assert_in_delta SkewT.saturation_vapor_pressure(20.0), 23.37, 0.2
# At -20°C es ≈ 1.254 hPa.
assert_in_delta SkewT.saturation_vapor_pressure(-20.0), 1.254, 0.05
end
end
describe "temperature_from_mixing_ratio/2" do
test "round-trips mixing ratios" do
# Pick a pressure and temperature, compute ws, invert, expect the original.
p = 850.0
for t <- [-10.0, 0.0, 15.0, 25.0] do
es = SkewT.saturation_vapor_pressure(t)
ws_g_kg = 622.0 * es / (p - es)
recovered = SkewT.temperature_from_mixing_ratio(ws_g_kg, p)
assert_in_delta recovered, t, 0.05
end
end
end
describe "dry_adiabat_temperature/2" do
test "θ == T at 1000 mb" do
# At the reference pressure the potential temperature equals the
# actual temperature — so θ = 300 K ⇒ T(1000) = 300 - 273.15 ≈ 26.85°C.
assert_in_delta SkewT.dry_adiabat_temperature(300.0, 1000.0), 26.85, 0.02
end
test "a parcel lifted dry-adiabatically cools" do
# Lifting from 1000 mb (θ = 290 K, T ≈ 16.85°C) to 700 mb should
# drop the temperature by ~26°C (roughly 9.8°C/km, ~3 km).
t_surface = SkewT.dry_adiabat_temperature(290.0, 1000.0)
t_700 = SkewT.dry_adiabat_temperature(290.0, 700.0)
assert t_surface - t_700 > 20.0
end
end
describe "project/3" do
setup do
chart = SkewT.chart_config(width: 400, height: 500)
%{chart: chart}
end
test "p_bot lands at the chart bottom and p_top at the top", %{chart: chart} do
{_x_bot, y_bot} = SkewT.project(0.0, chart.p_bot, chart)
{_x_top, y_top} = SkewT.project(0.0, chart.p_top, chart)
# SVG y grows downward, so p_bot (bottom) has a larger y than p_top.
assert y_bot > y_top
assert_in_delta y_bot, chart.padding_top + chart.plot_height, 0.5
assert_in_delta y_top, chart.padding_top, 0.5
end
test "isotherms skew right as pressure decreases", %{chart: chart} do
# Same temperature at two pressures should have a larger x at
# the lower pressure (top of chart) because of the skew.
{x_bot, _} = SkewT.project(0.0, chart.p_bot, chart)
{x_top, _} = SkewT.project(0.0, chart.p_top, chart)
assert x_top > x_bot
end
test "temperature range spans the bottom of the chart", %{chart: chart} do
# At p_bot, t_min lands at the left edge of the data area.
{x_min, _} = SkewT.project(chart.t_min, chart.p_bot, chart)
{x_max, _} = SkewT.project(chart.t_max, chart.p_bot, chart)
assert_in_delta x_min, chart.padding_left, 0.5
assert_in_delta x_max, chart.padding_left + chart.plot_width, 0.5
end
end
describe "build/2" do
test "with no profile returns nil" do
assert SkewT.build(nil) == nil
assert SkewT.build([]) == nil
end
test "returns a map of rendering primitives for a valid profile" do
profile = [
%{"pres" => 1000.0, "hght" => 100.0, "tmpc" => 25.0, "dwpc" => 18.0},
%{"pres" => 850.0, "hght" => 1500.0, "tmpc" => 15.0, "dwpc" => 10.0},
%{"pres" => 700.0, "hght" => 3100.0, "tmpc" => 5.0, "dwpc" => -2.0},
%{"pres" => 500.0, "hght" => 5600.0, "tmpc" => -15.0, "dwpc" => -25.0}
]
chart = SkewT.build(profile)
assert map_size(chart) > 0
assert chart.width > 0
assert chart.height > 0
# Background grid
assert chart.isobars != []
assert chart.isotherms != []
assert chart.dry_adiabats != []
assert chart.mixing_ratio_lines != []
# Traces
assert chart.t_points != []
assert chart.td_points != []
# The first plotted t_point should correspond to the highest pressure
# (surface-most) profile entry.
assert length(chart.t_points) == 4
end
test "filters out profile levels with missing temperature or dewpoint independently" do
profile = [
%{"pres" => 1000.0, "tmpc" => 25.0, "dwpc" => 18.0},
%{"pres" => 900.0, "tmpc" => nil, "dwpc" => 15.0},
%{"pres" => 850.0, "tmpc" => 15.0, "dwpc" => nil},
%{"pres" => 700.0, "tmpc" => 5.0, "dwpc" => -2.0}
]
chart = SkewT.build(profile)
# 3 levels have tmpc, 3 have dwpc — the traces are independent.
assert length(chart.t_points) == 3
assert length(chart.td_points) == 3
end
end
end