Skip to content

Commit ca550e7

Browse files
authored
Merge pull request #1581 from fsprojects/test-coverage-improvement-json-html-20250831
Improve test coverage for JSON Runtime Helpers and BaseTypes.HtmlDocument
2 parents ca6b3aa + 5e0de9a commit ca550e7

File tree

3 files changed

+353
-0
lines changed

3 files changed

+353
-0
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
module FSharp.Data.Tests.BaseTypesHtmlDocument
2+
3+
open System.IO
4+
open NUnit.Framework
5+
open FsUnit
6+
open FSharp.Data
7+
open FSharp.Data.Runtime.BaseTypes
8+
9+
#nowarn "10001" // Suppress "intended for use in generated code only" warnings
10+
11+
[<Test>]
12+
let ``HtmlDocument.Create successfully parses HTML with tables`` () =
13+
let htmlContent = """
14+
<html>
15+
<body>
16+
<table id="test-table">
17+
<tr><th>Name</th><th>Age</th></tr>
18+
<tr><td>John</td><td>30</td></tr>
19+
</table>
20+
</body>
21+
</html>"""
22+
23+
use reader = new StringReader(htmlContent)
24+
let htmlDoc = HtmlDocument.Create(false, reader)
25+
26+
htmlDoc.Html.ToString() |> should contain "test-table"
27+
28+
[<Test>]
29+
let ``HtmlDocument.Create with includeLayoutTables true`` () =
30+
let htmlContent = """
31+
<html>
32+
<body>
33+
<table id="layout-table">
34+
<tr><td>Layout cell</td></tr>
35+
</table>
36+
</body>
37+
</html>"""
38+
39+
use reader = new StringReader(htmlContent)
40+
let htmlDoc = HtmlDocument.Create(true, reader)
41+
42+
htmlDoc.Html.ToString() |> should contain "layout-table"
43+
44+
[<Test>]
45+
let ``HtmlDocument.Create with includeLayoutTables false`` () =
46+
let htmlContent = """
47+
<html>
48+
<body>
49+
<table id="data-table">
50+
<tr><td>Data cell</td></tr>
51+
</table>
52+
</body>
53+
</html>"""
54+
55+
use reader = new StringReader(htmlContent)
56+
let htmlDoc = HtmlDocument.Create(false, reader)
57+
58+
htmlDoc.Html.ToString() |> should contain "data-table"
59+
60+
[<Test>]
61+
let ``HtmlDocument.Html property returns the parsed document`` () =
62+
let htmlContent = """<html><body><h1>Test Title</h1></body></html>"""
63+
64+
use reader = new StringReader(htmlContent)
65+
let htmlDoc = HtmlDocument.Create(false, reader)
66+
let doc = htmlDoc.Html
67+
68+
doc.ToString() |> should contain "Test Title"
69+
70+
[<Test>]
71+
let ``HtmlDocument.GetTable retrieves table by id`` () =
72+
let htmlContent = """
73+
<html>
74+
<body>
75+
<table id="data-table">
76+
<tr><th>Column1</th><th>Column2</th></tr>
77+
<tr><td>Value1</td><td>Value2</td></tr>
78+
</table>
79+
</body>
80+
</html>"""
81+
82+
use reader = new StringReader(htmlContent)
83+
let htmlDoc = HtmlDocument.Create(false, reader)
84+
let table = htmlDoc.GetTable("data-table")
85+
86+
table.Name |> should equal "data-table"
87+
88+
[<Test>]
89+
let ``HtmlDocument.GetTable throws when table not found`` () =
90+
let htmlContent = """<html><body><p>No tables here</p></body></html>"""
91+
92+
use reader = new StringReader(htmlContent)
93+
let htmlDoc = HtmlDocument.Create(false, reader)
94+
95+
(fun () -> htmlDoc.GetTable("nonexistent") |> ignore) |> should throw typeof<System.Collections.Generic.KeyNotFoundException>
96+
97+
[<Test>]
98+
let ``HtmlDocument.GetList retrieves list by id`` () =
99+
let htmlContent = """
100+
<html>
101+
<body>
102+
<ul id="item-list">
103+
<li>Item 1</li>
104+
<li>Item 2</li>
105+
</ul>
106+
</body>
107+
</html>"""
108+
109+
use reader = new StringReader(htmlContent)
110+
let htmlDoc = HtmlDocument.Create(false, reader)
111+
let list = htmlDoc.GetList("item-list")
112+
113+
list.Name |> should equal "item-list"
114+
115+
[<Test>]
116+
let ``HtmlDocument.GetList works with ordered lists`` () =
117+
let htmlContent = """
118+
<html>
119+
<body>
120+
<ol id="numbered-list">
121+
<li>First</li>
122+
<li>Second</li>
123+
</ol>
124+
</body>
125+
</html>"""
126+
127+
use reader = new StringReader(htmlContent)
128+
let htmlDoc = HtmlDocument.Create(false, reader)
129+
let list = htmlDoc.GetList("numbered-list")
130+
131+
list.Name |> should equal "numbered-list"
132+
133+
[<Test>]
134+
let ``HtmlDocument.GetList throws when list not found`` () =
135+
let htmlContent = """<html><body><p>No lists here</p></body></html>"""
136+
137+
use reader = new StringReader(htmlContent)
138+
let htmlDoc = HtmlDocument.Create(false, reader)
139+
140+
(fun () -> htmlDoc.GetList("nonexistent") |> ignore) |> should throw typeof<System.Collections.Generic.KeyNotFoundException>
141+
142+
[<Test>]
143+
let ``HtmlDocument.GetDefinitionList retrieves definition list by id`` () =
144+
let htmlContent = """
145+
<html>
146+
<body>
147+
<dl id="def-list">
148+
<dt>Term1</dt>
149+
<dd>Definition1</dd>
150+
<dt>Term2</dt>
151+
<dd>Definition2</dd>
152+
</dl>
153+
</body>
154+
</html>"""
155+
156+
use reader = new StringReader(htmlContent)
157+
let htmlDoc = HtmlDocument.Create(false, reader)
158+
let defList = htmlDoc.GetDefinitionList("def-list")
159+
160+
defList.Name |> should equal "def-list"
161+
162+
[<Test>]
163+
let ``HtmlDocument.GetDefinitionList throws when definition list not found`` () =
164+
let htmlContent = """<html><body><p>No definition lists here</p></body></html>"""
165+
166+
use reader = new StringReader(htmlContent)
167+
let htmlDoc = HtmlDocument.Create(false, reader)
168+
169+
(fun () -> htmlDoc.GetDefinitionList("nonexistent") |> ignore) |> should throw typeof<System.Collections.Generic.KeyNotFoundException>
170+
171+
[<Test>]
172+
let ``HtmlDocument.Create handles empty HTML`` () =
173+
let htmlContent = "<html><body></body></html>"
174+
175+
use reader = new StringReader(htmlContent)
176+
let htmlDoc = HtmlDocument.Create(false, reader)
177+
178+
htmlDoc.Html.ToString() |> should not' (equal "")
179+
180+
[<Test>]
181+
let ``HtmlDocument.Create handles malformed HTML gracefully`` () =
182+
let htmlContent = "<html><body><h1>Test Content</h1><p>Valid paragraph</p><div>Unclosed div"
183+
184+
use reader = new StringReader(htmlContent)
185+
let htmlDoc = HtmlDocument.Create(false, reader)
186+
187+
// Parser handles malformed HTML by auto-closing tags and preserving content
188+
let htmlString = htmlDoc.Html.ToString()
189+
htmlString |> should contain "Test Content"
190+
htmlString |> should contain "Valid paragraph"
191+
// The parser should preserve at least some structure even with malformed HTML
192+
193+
[<Test>]
194+
let ``HtmlDocument.Create processes multiple tables correctly`` () =
195+
let htmlContent = """
196+
<html>
197+
<body>
198+
<table id="table1">
199+
<tr><td>Table 1 Content</td></tr>
200+
</table>
201+
<table id="table2">
202+
<tr><td>Table 2 Content</td></tr>
203+
</table>
204+
</body>
205+
</html>"""
206+
207+
use reader = new StringReader(htmlContent)
208+
let htmlDoc = HtmlDocument.Create(false, reader)
209+
210+
// Use the actual generated names since HTML parsing creates unique names
211+
htmlDoc.Html.ToString() |> should contain "Table 1 Content"
212+
htmlDoc.Html.ToString() |> should contain "Table 2 Content"
213+
214+
[<Test>]
215+
let ``HtmlDocument.Create processes multiple lists correctly`` () =
216+
let htmlContent = """
217+
<html>
218+
<body>
219+
<ul id="list1">
220+
<li>List 1 Item</li>
221+
</ul>
222+
<ol id="list2">
223+
<li>List 2 Item</li>
224+
</ol>
225+
</body>
226+
</html>"""
227+
228+
use reader = new StringReader(htmlContent)
229+
let htmlDoc = HtmlDocument.Create(false, reader)
230+
231+
// Verify the content is parsed correctly
232+
htmlDoc.Html.ToString() |> should contain "List 1 Item"
233+
htmlDoc.Html.ToString() |> should contain "List 2 Item"
234+
235+
[<Test>]
236+
let ``HtmlDocument.Create processes multiple definition lists correctly`` () =
237+
let htmlContent = """
238+
<html>
239+
<body>
240+
<dl id="def1">
241+
<dt>Term A</dt>
242+
<dd>Definition A</dd>
243+
</dl>
244+
<dl id="def2">
245+
<dt>Term B</dt>
246+
<dd>Definition B</dd>
247+
</dl>
248+
</body>
249+
</html>"""
250+
251+
use reader = new StringReader(htmlContent)
252+
let htmlDoc = HtmlDocument.Create(false, reader)
253+
254+
// Verify the definition lists are parsed correctly
255+
htmlDoc.Html.ToString() |> should contain "Term A"
256+
htmlDoc.Html.ToString() |> should contain "Definition A"
257+
htmlDoc.Html.ToString() |> should contain "Term B"
258+
htmlDoc.Html.ToString() |> should contain "Definition B"

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<Compile Include="HtmlOperations.fs" />
3939
<Compile Include="HtmlAttributeExtensions.fs" />
4040
<Compile Include="HtmlCssSelectors.fs" />
41+
<Compile Include="BaseTypesHtmlDocument.fs" />
4142
<Compile Include="XmlExtensions.fs" />
4243
<Compile Include="WorldBankRuntime.fs" />
4344
<Compile Include="Program.fs" />

tests/FSharp.Data.Core.Tests/JsonConversions.fs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,97 @@ let ``Conversions handle different number formats`` () =
196196
JsonValue.String "123.456" |> asDecimal |> should equal (Some 123.456M)
197197
JsonValue.String "0" |> asDecimal |> should equal (Some 0M)
198198
JsonValue.String "-99.99" |> asDecimal |> should equal (Some -99.99M)
199+
200+
[<Test>]
201+
let ``Integer conversions test range boundaries for helper functions`` () =
202+
let asInteger = JsonConversions.AsInteger System.Globalization.CultureInfo.InvariantCulture
203+
let asInteger64 = JsonConversions.AsInteger64 System.Globalization.CultureInfo.InvariantCulture
204+
205+
// Test exact boundary values to trigger inRangeDecimal helper function
206+
JsonValue.Number (decimal System.Int32.MaxValue) |> asInteger |> should equal (Some System.Int32.MaxValue)
207+
JsonValue.Number (decimal System.Int32.MinValue) |> asInteger |> should equal (Some System.Int32.MinValue)
208+
209+
// Test values just outside boundaries to trigger inRangeDecimal helper function
210+
JsonValue.Number ((decimal System.Int32.MaxValue) + 1M) |> asInteger |> should equal None
211+
JsonValue.Number ((decimal System.Int32.MinValue) - 1M) |> asInteger |> should equal None
212+
213+
// Test exact boundary values for Int64 to trigger inRangeDecimal helper function
214+
JsonValue.Number (decimal System.Int64.MaxValue) |> asInteger64 |> should equal (Some System.Int64.MaxValue)
215+
JsonValue.Number (decimal System.Int64.MinValue) |> asInteger64 |> should equal (Some System.Int64.MinValue)
216+
217+
[<Test>]
218+
let ``Float integer conversions test range boundaries for helper functions`` () =
219+
let asInteger = JsonConversions.AsInteger System.Globalization.CultureInfo.InvariantCulture
220+
let asInteger64 = JsonConversions.AsInteger64 System.Globalization.CultureInfo.InvariantCulture
221+
222+
// Test exact boundary values with floats to trigger inRangeFloat helper function
223+
JsonValue.Float (float System.Int32.MaxValue) |> asInteger |> should equal (Some System.Int32.MaxValue)
224+
JsonValue.Float (float System.Int32.MinValue) |> asInteger |> should equal (Some System.Int32.MinValue)
225+
226+
// Test values just outside boundaries with floats to trigger inRangeFloat helper function
227+
JsonValue.Float ((float System.Int32.MaxValue) + 1.0) |> asInteger |> should equal None
228+
JsonValue.Float ((float System.Int32.MinValue) - 1.0) |> asInteger |> should equal None
229+
230+
// Test large values for Int64 with floats to trigger inRangeFloat helper function
231+
// Use values that don't hit floating-point precision limits
232+
JsonValue.Float 1000000000000.0 |> asInteger64 |> should equal (Some 1000000000000L)
233+
JsonValue.Float -1000000000000.0 |> asInteger64 |> should equal (Some -1000000000000L)
234+
235+
[<Test>]
236+
let ``Integer detection tests for decimal values to trigger isIntegerDecimal helper`` () =
237+
let asInteger = JsonConversions.AsInteger System.Globalization.CultureInfo.InvariantCulture
238+
let asInteger64 = JsonConversions.AsInteger64 System.Globalization.CultureInfo.InvariantCulture
239+
240+
// Test decimal values that are integers to trigger isIntegerDecimal helper function
241+
JsonValue.Number 42.0M |> asInteger |> should equal (Some 42)
242+
JsonValue.Number -123.000M |> asInteger |> should equal (Some -123)
243+
JsonValue.Number 0.0M |> asInteger |> should equal (Some 0)
244+
JsonValue.Number 1.0M |> asInteger64 |> should equal (Some 1L)
245+
246+
// Test decimal values that are NOT integers to trigger isIntegerDecimal helper function
247+
JsonValue.Number 42.1M |> asInteger |> should equal None
248+
JsonValue.Number -123.5M |> asInteger |> should equal None
249+
JsonValue.Number 0.001M |> asInteger |> should equal None
250+
JsonValue.Number 1.99999M |> asInteger64 |> should equal None
251+
252+
[<Test>]
253+
let ``Integer detection tests for float values to trigger isIntegerFloat helper`` () =
254+
let asInteger = JsonConversions.AsInteger System.Globalization.CultureInfo.InvariantCulture
255+
let asInteger64 = JsonConversions.AsInteger64 System.Globalization.CultureInfo.InvariantCulture
256+
257+
// Test float values that are integers to trigger isIntegerFloat helper function
258+
JsonValue.Float 42.0 |> asInteger |> should equal (Some 42)
259+
JsonValue.Float -123.000 |> asInteger |> should equal (Some -123)
260+
JsonValue.Float 0.0 |> asInteger |> should equal (Some 0)
261+
JsonValue.Float 1.0 |> asInteger64 |> should equal (Some 1L)
262+
263+
// Test float values that are NOT integers to trigger isIntegerFloat helper function
264+
JsonValue.Float 42.1 |> asInteger |> should equal None
265+
JsonValue.Float -123.5 |> asInteger |> should equal None
266+
JsonValue.Float 0.001 |> asInteger |> should equal None
267+
JsonValue.Float 1.99999 |> asInteger64 |> should equal None
268+
269+
// Test special float cases to trigger isIntegerFloat helper function
270+
JsonValue.Float System.Double.NaN |> asInteger |> should equal None
271+
JsonValue.Float System.Double.PositiveInfinity |> asInteger |> should equal None
272+
JsonValue.Float System.Double.NegativeInfinity |> asInteger64 |> should equal None
273+
274+
[<Test>]
275+
let ``Combined edge cases to exercise all helper functions`` () =
276+
let asInteger = JsonConversions.AsInteger System.Globalization.CultureInfo.InvariantCulture
277+
let asInteger64 = JsonConversions.AsInteger64 System.Globalization.CultureInfo.InvariantCulture
278+
279+
// Large integers that exercise both range checking and integer detection helpers
280+
JsonValue.Number 2147483647.0M |> asInteger |> should equal (Some 2147483647) // Int32.MaxValue
281+
JsonValue.Float 2147483647.0 |> asInteger |> should equal (Some 2147483647)
282+
283+
JsonValue.Number 2147483648.0M |> asInteger |> should equal None // Just over Int32.MaxValue
284+
JsonValue.Float 2147483648.0 |> asInteger |> should equal None
285+
286+
// Large values that work for Int64 but not Int32
287+
JsonValue.Number 3000000000M |> asInteger |> should equal None
288+
JsonValue.Number 3000000000M |> asInteger64 |> should equal (Some 3000000000L)
289+
290+
// Values that are in range but not integers
291+
JsonValue.Number 100.5M |> asInteger |> should equal None
292+
JsonValue.Float 100.5 |> asInteger |> should equal None

0 commit comments

Comments
 (0)