Skip to content

Commit 5fd7890

Browse files
authored
Merge pull request #1757 from fsprojects/repo-assist/test-htmlinference-2026-04-23-d7c00d2a236baee0
[Repo Assist] test: add 23 unit tests for HtmlInference.inferListType and inferHeaders
2 parents e45a7b6 + 912ec5d commit 5fd7890

2 files changed

Lines changed: 181 additions & 0 deletions

File tree

tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
<Compile Include="HtmlAttributeExtensions.fs" />
4545
<Compile Include="HtmlCssSelectors.fs" />
4646
<Compile Include="HtmlTableCell.fs" />
47+
<Compile Include="HtmlInference.fs" />
4748
<Compile Include="StructuralInference.fs" />
4849
<Compile Include="StructuralTypes.fs" />
4950
<Compile Include="BaseTypesHtmlDocument.fs" />
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
module FSharp.Data.Tests.HtmlInference
2+
3+
open System
4+
open System.Globalization
5+
open NUnit.Framework
6+
open FsUnit
7+
open FSharp.Data
8+
open FSharp.Data.Runtime
9+
open FSharp.Data.Runtime.StructuralTypes
10+
open FSharp.Data.Runtime.StructuralInference
11+
12+
// ─── Helpers ────────────────────────────────────────────────────────────────
13+
14+
let private mkParams preferOptionals : HtmlInference.Parameters =
15+
{ MissingValues = TextConversions.DefaultMissingValues
16+
CultureInfo = CultureInfo.InvariantCulture
17+
UnitsOfMeasureProvider = defaultUnitsOfMeasureProvider
18+
PreferOptionals = preferOptionals
19+
InferenceMode = InferenceMode'.ValuesOnly }
20+
21+
let private defaultParams = mkParams false
22+
let private optionalParams = mkParams true
23+
24+
let private prim typ =
25+
InferedType.Primitive(typ, None, false, false)
26+
27+
// ─── inferListType ───────────────────────────────────────────────────────────
28+
29+
[<Test>]
30+
let ``inferListType returns Null for empty array`` () =
31+
HtmlInference.inferListType defaultParams [||] |> should equal InferedType.Null
32+
33+
[<Test>]
34+
let ``inferListType returns Null for whitespace-only values`` () =
35+
HtmlInference.inferListType defaultParams [| " "; "\t"; "" |]
36+
|> should equal InferedType.Null
37+
38+
[<Test>]
39+
let ``inferListType treats nbsp as missing value`` () =
40+
HtmlInference.inferListType defaultParams [| "&nbsp;"; "&nbsp" |]
41+
|> should equal InferedType.Null
42+
43+
[<Test>]
44+
let ``inferListType infers int type`` () =
45+
HtmlInference.inferListType defaultParams [| "1"; "2"; "3" |]
46+
|> should equal (prim typeof<int>)
47+
48+
[<Test>]
49+
let ``inferListType infers decimal type`` () =
50+
HtmlInference.inferListType defaultParams [| "1.5"; "2.3"; "3.7" |]
51+
|> should equal (prim typeof<decimal>)
52+
53+
[<Test>]
54+
let ``inferListType infers float type for scientific notation`` () =
55+
HtmlInference.inferListType defaultParams [| "1e100"; "2.5e-10" |]
56+
|> should equal (prim typeof<float>)
57+
58+
[<Test>]
59+
let ``inferListType infers string type`` () =
60+
HtmlInference.inferListType defaultParams [| "hello"; "world" |]
61+
|> should equal (prim typeof<string>)
62+
63+
[<Test>]
64+
let ``inferListType infers bool type`` () =
65+
HtmlInference.inferListType defaultParams [| "true"; "false" |]
66+
|> should equal (prim typeof<bool>)
67+
68+
[<Test>]
69+
let ``inferListType widens int to decimal when mixed with decimal`` () =
70+
HtmlInference.inferListType defaultParams [| "1"; "2.5"; "3" |]
71+
|> should equal (prim typeof<decimal>)
72+
73+
[<Test>]
74+
let ``inferListType widens to float for scientific notation mixed with int`` () =
75+
HtmlInference.inferListType defaultParams [| "1"; "1.5e10"; "3" |]
76+
|> should equal (prim typeof<float>)
77+
78+
[<Test>]
79+
let ``inferListType produces Heterogeneous type for mixed numeric and non-numeric`` () =
80+
let result = HtmlInference.inferListType defaultParams [| "42"; "hello" |]
81+
82+
match result with
83+
| InferedType.Heterogeneous _ -> ()
84+
| _ -> Assert.Fail(sprintf "Expected Heterogeneous, got %A" result)
85+
86+
[<Test>]
87+
let ``inferListType treats NaN as float when preferOptionals is false`` () =
88+
let result = HtmlInference.inferListType defaultParams [| "NaN" |]
89+
result |> should equal (prim typeof<float>)
90+
91+
[<Test>]
92+
let ``inferListType treats NaN as Null when preferOptionals is true`` () =
93+
let result = HtmlInference.inferListType optionalParams [| "NaN" |]
94+
result |> should equal InferedType.Null
95+
96+
[<Test>]
97+
let ``inferListType treats NA as float when preferOptionals is false`` () =
98+
let result = HtmlInference.inferListType defaultParams [| "NA" |]
99+
result |> should equal (prim typeof<float>)
100+
101+
[<Test>]
102+
let ``inferListType infers date type for ISO date strings`` () =
103+
let result =
104+
HtmlInference.inferListType defaultParams [| "2023-01-01"; "2023-06-15" |]
105+
106+
match result with
107+
| InferedType.Primitive(typ, _, false, _) ->
108+
(typ = typeof<DateTime> || typ = typeof<DateOnly>)
109+
|> should equal true
110+
| _ -> Assert.Fail(sprintf "Expected date primitive, got %A" result)
111+
112+
[<Test>]
113+
let ``inferListType with mixed missing and integer values infers float (allowEmptyValues path)`` () =
114+
// NaN + int values → float because NaN is treated as float when preferOptionals=false
115+
let result = HtmlInference.inferListType defaultParams [| "NaN"; "1"; "2" |]
116+
result |> should equal (prim typeof<float>)
117+
118+
[<Test>]
119+
let ``inferListType with single integer value returns int`` () =
120+
HtmlInference.inferListType defaultParams [| "42" |]
121+
|> should equal (prim typeof<int>)
122+
123+
// ─── inferHeaders ────────────────────────────────────────────────────────────
124+
125+
[<Test>]
126+
let ``inferHeaders returns false for empty rows`` () =
127+
let hasHeaders, names, units, _ = HtmlInference.inferHeaders defaultParams [||]
128+
hasHeaders |> should equal false
129+
names |> should equal None
130+
units |> should equal None
131+
132+
[<Test>]
133+
let ``inferHeaders returns false for single row`` () =
134+
let hasHeaders, names, _, _ =
135+
HtmlInference.inferHeaders defaultParams [| [| "Name"; "Age" |] |]
136+
137+
hasHeaders |> should equal false
138+
names |> should equal None
139+
140+
[<Test>]
141+
let ``inferHeaders returns false for exactly two rows`` () =
142+
let rows = [| [| "Name"; "Age" |]; [| "Alice"; "30" |] |]
143+
let hasHeaders, names, _, _ = HtmlInference.inferHeaders defaultParams rows
144+
hasHeaders |> should equal false
145+
names |> should equal None
146+
147+
[<Test>]
148+
let ``inferHeaders returns true when header row differs from data rows`` () =
149+
// First row is text (string) headers, data rows are numeric → types differ → headers detected
150+
let rows =
151+
[| [| "Name"; "Score" |]
152+
[| "Alice"; "95" |]
153+
[| "Bob"; "87" |] |]
154+
155+
let hasHeaders, names, _, _ = HtmlInference.inferHeaders defaultParams rows
156+
hasHeaders |> should equal true
157+
names |> should equal (Some [| "Name"; "Score" |])
158+
159+
[<Test>]
160+
let ``inferHeaders returns false when all rows have same type`` () =
161+
// All rows are strings → header row type = data rows type → no headers inferred
162+
let rows =
163+
[| [| "Alice"; "Bob" |]
164+
[| "Carol"; "Dave" |]
165+
[| "Eve"; "Frank" |] |]
166+
167+
let hasHeaders, names, _, _ = HtmlInference.inferHeaders defaultParams rows
168+
hasHeaders |> should equal false
169+
names |> should equal None
170+
171+
[<Test>]
172+
let ``inferHeaders returns inferred type for data rows`` () =
173+
let rows =
174+
[| [| "Name"; "Age" |]
175+
[| "Alice"; "30" |]
176+
[| "Bob"; "25" |] |]
177+
178+
let hasHeaders, _, _, dataType = HtmlInference.inferHeaders defaultParams rows
179+
hasHeaders |> should equal true
180+
dataType |> should not' (equal None)

0 commit comments

Comments
 (0)