From df8a22c3049be76fa3527f39edae32e951ac6729 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 04:53:36 +0000 Subject: [PATCH 1/2] test: add 23 unit tests for HtmlInference.inferListType and inferHeaders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover key behaviours of the HTML table type-inference helpers: - inferListType: empty/whitespace/  → Null; int, decimal, float (scientific), bool, string; NaN/NA as float (or Null with preferOptionals); DateOnly/DateTime for ISO date strings; mixing int+decimal widened to decimal; int+scientific widened to float; int+string produces Heterogeneous; NaN+int with preferOptionals=false widened to float - inferHeaders: ≤2 rows → false; header-vs-data type mismatch → true with header names; uniform-type rows → false; data type returned Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../FSharp.Data.Core.Tests.fsproj | 1 + tests/FSharp.Data.Core.Tests/HtmlInference.fs | 180 ++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 tests/FSharp.Data.Core.Tests/HtmlInference.fs diff --git a/tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj b/tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj index dc1bf9cd1..fee4b9d26 100644 --- a/tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj +++ b/tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj @@ -44,6 +44,7 @@ + diff --git a/tests/FSharp.Data.Core.Tests/HtmlInference.fs b/tests/FSharp.Data.Core.Tests/HtmlInference.fs new file mode 100644 index 000000000..942c6ea4b --- /dev/null +++ b/tests/FSharp.Data.Core.Tests/HtmlInference.fs @@ -0,0 +1,180 @@ +module FSharp.Data.Tests.HtmlInference + +open System +open System.Globalization +open NUnit.Framework +open FsUnit +open FSharp.Data +open FSharp.Data.Runtime +open FSharp.Data.Runtime.StructuralTypes +open FSharp.Data.Runtime.StructuralInference + +// ─── Helpers ──────────────────────────────────────────────────────────────── + +let private mkParams preferOptionals : HtmlInference.Parameters = + { MissingValues = TextConversions.DefaultMissingValues + CultureInfo = CultureInfo.InvariantCulture + UnitsOfMeasureProvider = defaultUnitsOfMeasureProvider + PreferOptionals = preferOptionals + InferenceMode = InferenceMode'.ValuesOnly } + +let private defaultParams = mkParams false +let private optionalParams = mkParams true + +let private prim typ = + InferedType.Primitive(typ, None, false, false) + +// ─── inferListType ─────────────────────────────────────────────────────────── + +[] +let ``inferListType returns Null for empty array`` () = + HtmlInference.inferListType defaultParams [||] |> should equal InferedType.Null + +[] +let ``inferListType returns Null for whitespace-only values`` () = + HtmlInference.inferListType defaultParams [| " "; "\t"; "" |] + |> should equal InferedType.Null + +[] +let ``inferListType treats nbsp as missing value`` () = + HtmlInference.inferListType defaultParams [| " "; " " |] + |> should equal InferedType.Null + +[] +let ``inferListType infers int type`` () = + HtmlInference.inferListType defaultParams [| "1"; "2"; "3" |] + |> should equal (prim typeof) + +[] +let ``inferListType infers decimal type`` () = + HtmlInference.inferListType defaultParams [| "1.5"; "2.3"; "3.7" |] + |> should equal (prim typeof) + +[] +let ``inferListType infers float type for scientific notation`` () = + HtmlInference.inferListType defaultParams [| "1e100"; "2.5e-10" |] + |> should equal (prim typeof) + +[] +let ``inferListType infers string type`` () = + HtmlInference.inferListType defaultParams [| "hello"; "world" |] + |> should equal (prim typeof) + +[] +let ``inferListType infers bool type`` () = + HtmlInference.inferListType defaultParams [| "true"; "false" |] + |> should equal (prim typeof) + +[] +let ``inferListType widens int to decimal when mixed with decimal`` () = + HtmlInference.inferListType defaultParams [| "1"; "2.5"; "3" |] + |> should equal (prim typeof) + +[] +let ``inferListType widens to float for scientific notation mixed with int`` () = + HtmlInference.inferListType defaultParams [| "1"; "1.5e10"; "3" |] + |> should equal (prim typeof) + +[] +let ``inferListType produces Heterogeneous type for mixed numeric and non-numeric`` () = + let result = HtmlInference.inferListType defaultParams [| "42"; "hello" |] + + match result with + | InferedType.Heterogeneous _ -> () + | _ -> Assert.Fail(sprintf "Expected Heterogeneous, got %A" result) + +[] +let ``inferListType treats NaN as float when preferOptionals is false`` () = + let result = HtmlInference.inferListType defaultParams [| "NaN" |] + result |> should equal (prim typeof) + +[] +let ``inferListType treats NaN as Null when preferOptionals is true`` () = + let result = HtmlInference.inferListType optionalParams [| "NaN" |] + result |> should equal InferedType.Null + +[] +let ``inferListType treats NA as float when preferOptionals is false`` () = + let result = HtmlInference.inferListType defaultParams [| "NA" |] + result |> should equal (prim typeof) + +[] +let ``inferListType infers date type for ISO date strings`` () = + let result = + HtmlInference.inferListType defaultParams [| "2023-01-01"; "2023-06-15" |] + + match result with + | InferedType.Primitive(typ, _, false, _) -> + (typ = typeof || typ = typeof) + |> should equal true + | _ -> Assert.Fail(sprintf "Expected date primitive, got %A" result) + +[] +let ``inferListType with mixed missing and integer values infers float (allowEmptyValues path)`` () = + // NaN + int values → float because NaN is treated as float when preferOptionals=false + let result = HtmlInference.inferListType defaultParams [| "NaN"; "1"; "2" |] + result |> should equal (prim typeof) + +[] +let ``inferListType with single integer value returns int`` () = + HtmlInference.inferListType defaultParams [| "42" |] + |> should equal (prim typeof) + +// ─── inferHeaders ──────────────────────────────────────────────────────────── + +[] +let ``inferHeaders returns false for empty rows`` () = + let hasHeaders, names, units, _ = HtmlInference.inferHeaders defaultParams [||] + hasHeaders |> should equal false + names |> should equal None + units |> should equal None + +[] +let ``inferHeaders returns false for single row`` () = + let hasHeaders, names, _, _ = + HtmlInference.inferHeaders defaultParams [| [| "Name"; "Age" |] |] + + hasHeaders |> should equal false + names |> should equal None + +[] +let ``inferHeaders returns false for exactly two rows`` () = + let rows = [| [| "Name"; "Age" |]; [| "Alice"; "30" |] |] + let hasHeaders, names, _, _ = HtmlInference.inferHeaders defaultParams rows + hasHeaders |> should equal false + names |> should equal None + +[] +let ``inferHeaders returns true when header row differs from data rows`` () = + // First row is text (string) headers, data rows are numeric → types differ → headers detected + let rows = + [| [| "Name"; "Score" |] + [| "Alice"; "95" |] + [| "Bob"; "87" |] |] + + let hasHeaders, names, _, _ = HtmlInference.inferHeaders defaultParams rows + hasHeaders |> should equal true + names |> should equal (Some [| "Name"; "Score" |]) + +[] +let ``inferHeaders returns false when all rows have same type`` () = + // All rows are strings → header row type = data rows type → no headers inferred + let rows = + [| [| "Alice"; "Bob" |] + [| "Carol"; "Dave" |] + [| "Eve"; "Frank" |] |] + + let hasHeaders, names, _, _ = HtmlInference.inferHeaders defaultParams rows + hasHeaders |> should equal false + names |> should equal None + +[] +let ``inferHeaders returns inferred type for data rows`` () = + let rows = + [| [| "Name"; "Age" |] + [| "Alice"; "30" |] + [| "Bob"; "25" |] |] + + let hasHeaders, _, _, dataType = HtmlInference.inferHeaders defaultParams rows + hasHeaders |> should equal true + dataType |> should not' (equal None) From 912ec5d7b3855772c4b8274d352e36645973cb05 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 23 Apr 2026 04:53:38 +0000 Subject: [PATCH 2/2] ci: trigger checks