feat: Add Python consistency tests for calculator definitions and refactor QuickConversionTable to explicitly pass calculator configuration to its row builder.

This commit is contained in:
Ben
2026-03-08 13:41:50 -07:00
parent 193affca27
commit 2794835590
3 changed files with 113 additions and 5 deletions

View File

@@ -8,8 +8,8 @@
type Row = { input: number; output: string };
const buildRow = (value: number): Row => {
const formatted = solve(config, 1, value.toString(), '', '');
const buildRow = (value: number, c: CalculatorDef): Row => {
const formatted = solve(c, 1, value.toString(), '', '');
return {
input: value,
output: formatted.val2 || '—',
@@ -22,8 +22,8 @@
let outputLabel = 'target units';
$: supportsTable = ['standard', 'inverse'].includes(config.type);
$: rows = supportsTable
? numericSamples.map(buildRow)
$: rows = (config && supportsTable)
? numericSamples.map(v => buildRow(v, config))
: [];
$: inputLabel = config.labels?.in1 ?? 'source units';
$: outputLabel = config.labels?.in2 ?? 'target units';

View File

@@ -82,7 +82,9 @@
<h1 class="page-title calculator-page-title">{calc.name}</h1>
{#key calc.slug}
<Calculator config={calc} showTitle={false} />
{/key}
<div class="seo-content">
{#if calc.descriptionHTML}

106
tests/test_consistency.py Normal file
View File

@@ -0,0 +1,106 @@
import math
import re
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CALCULATORS_TS = ROOT / "hdyc-svelte" / "src" / "lib" / "data" / "calculators.ts"
def _js_fmt(n: float) -> str:
"""Mimics the fmt() function in engine.ts"""
if not math.isfinite(n):
return str(n)
if n == 0:
return "0"
if abs(n) < 1e-6:
return f"{n:.6e}".replace("e-0", "e-").replace("e+0", "e+")
# engine.ts uses parseFloat(n.toFixed(6)).toString()
rounded = round(n, 6)
if rounded == 0: # Handle -0.0
return "0"
if rounded == int(rounded):
return str(int(rounded))
return str(rounded)
def _js_fmt_precise(n: float) -> str:
"""Mimics formatExampleValue in QuickConversionExample.svelte"""
if n is None or math.isnan(n):
return ""
if not math.isfinite(n):
return str(n)
if n == 0:
return "0"
rounded = round(n, 6)
if rounded != 0:
if rounded == int(rounded):
return str(int(rounded))
return str(rounded)
# Precise version for very small numbers
precise = f"{n:.12f}".rstrip('0').rstrip('.')
return precise if precise else "0"
class TestCalculatorsConsistency(unittest.TestCase):
def test_standard_calculators_consistency(self):
text = CALCULATORS_TS.read_text(encoding="utf-8")
# Extract the calculators array content
match = re.search(r"export const calculators: CalculatorDef\[\] = \[(.*?)\];", text, re.S)
self.assertTrue(match, "Could not find calculators array in calculators.ts")
body = match.group(1)
# Split by '{"slug":' to avoid splitting on nested braces
raw_entries = body.split('{"slug":')
errors = []
for raw_entry in raw_entries:
if not raw_entry.strip():
continue
entry = '{"slug":' + raw_entry
slug_match = re.search(r'"slug": "(.*?)"', entry)
if not slug_match:
continue
slug = slug_match.group(1)
type_match = re.search(r'"type": "(.*?)"', entry)
if not type_match or type_match.group(1) != "standard":
continue
# Use non-greedy search for factor/offset and handle potential whitespace
factor_match = re.search(r'"factor":\s*([0-9.eE+-]+)', entry)
offset_match = re.search(r'"offset":\s*([0-9.eE+-]+)', entry)
factor = float(factor_match.group(1)) if factor_match else 1.0
offset = float(offset_match.group(1)) if offset_match else 0.0
# 1. Formula Hint vs Chart Row 1 Consistency
# Logic: solve(config, 1, "1") -> val2 = fmt(1 * factor + offset)
row_one_output = _js_fmt(1.0 * factor + offset)
# 2. How to convert Examples Consistency (Inverse)
if factor != 0:
reverse_val = (1.0 - offset) / factor
formatted_reverse = _js_fmt_precise(reverse_val)
# Specific check for Réaumur to Kelvin (User request)
if slug == "reaumur-to-kelvin":
if formatted_reverse != "-217.72":
errors.append(f"[{slug}] Reverse example mismatch: expected -217.72, got {formatted_reverse}")
if row_one_output != "274.4":
errors.append(f"[{slug}] Chart row 1 mismatch: expected 274.4, got {row_one_output}")
# Specific check for Feet per minute to Knots (Previous bug fix)
if slug == "feet-per-minute-to-knots":
if row_one_output != "0.009875":
errors.append(f"[{slug}] Chart row 1 mismatch: expected 0.009875, got {row_one_output}")
if formatted_reverse != "101.268504":
errors.append(f"[{slug}] Reverse example mismatch: expected 101.268504, got {formatted_reverse}")
if errors:
self.fail("\n" + "\n".join(errors))
if __name__ == "__main__":
unittest.main()