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:
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
type Row = { input: number; output: string };
|
type Row = { input: number; output: string };
|
||||||
|
|
||||||
const buildRow = (value: number): Row => {
|
const buildRow = (value: number, c: CalculatorDef): Row => {
|
||||||
const formatted = solve(config, 1, value.toString(), '', '');
|
const formatted = solve(c, 1, value.toString(), '', '');
|
||||||
return {
|
return {
|
||||||
input: value,
|
input: value,
|
||||||
output: formatted.val2 || '—',
|
output: formatted.val2 || '—',
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
let outputLabel = 'target units';
|
let outputLabel = 'target units';
|
||||||
|
|
||||||
$: supportsTable = ['standard', 'inverse'].includes(config.type);
|
$: supportsTable = ['standard', 'inverse'].includes(config.type);
|
||||||
$: rows = supportsTable
|
$: rows = (config && supportsTable)
|
||||||
? numericSamples.map(buildRow)
|
? numericSamples.map(v => buildRow(v, config))
|
||||||
: [];
|
: [];
|
||||||
$: inputLabel = config.labels?.in1 ?? 'source units';
|
$: inputLabel = config.labels?.in1 ?? 'source units';
|
||||||
$: outputLabel = config.labels?.in2 ?? 'target units';
|
$: outputLabel = config.labels?.in2 ?? 'target units';
|
||||||
|
|||||||
@@ -82,7 +82,9 @@
|
|||||||
|
|
||||||
<h1 class="page-title calculator-page-title">{calc.name}</h1>
|
<h1 class="page-title calculator-page-title">{calc.name}</h1>
|
||||||
|
|
||||||
<Calculator config={calc} showTitle={false} />
|
{#key calc.slug}
|
||||||
|
<Calculator config={calc} showTitle={false} />
|
||||||
|
{/key}
|
||||||
|
|
||||||
<div class="seo-content">
|
<div class="seo-content">
|
||||||
{#if calc.descriptionHTML}
|
{#if calc.descriptionHTML}
|
||||||
|
|||||||
106
tests/test_consistency.py
Normal file
106
tests/test_consistency.py
Normal 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()
|
||||||
Reference in New Issue
Block a user