Skip to content

Commit 26fffc6

Browse files
authored
Merge pull request #1770 from fsprojects/repo-assist/test-json-option-extensions-2026-04-30-028f4c4652b6226b
[Repo Assist] test: add 28 unit tests for JsonExtensions.InnerText and JsonValue.Properties
2 parents c2b5f33 + 61e56c3 commit 26fffc6

2 files changed

Lines changed: 171 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
@@ -35,6 +35,7 @@
3535
<Compile Include="JsonDocument.fs" />
3636
<Compile Include="JsonRuntime.fs" />
3737
<Compile Include="JsonSchema.fs" />
38+
<Compile Include="JsonValueOptionExtensions.fs" />
3839
<Compile Include="CsvReader.fs" />
3940
<Compile Include="CsvFile.fs" />
4041
<Compile Include="CsvRuntimeOperations.fs" />
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
module FSharp.Data.Tests.JsonValueInnerTextAndExtensions
2+
3+
open NUnit.Framework
4+
open FsUnit
5+
open System
6+
open System.Globalization
7+
open FSharp.Data
8+
open FSharp.Data.JsonExtensions
9+
10+
// ============================================================
11+
// JsonExtensions.InnerText — C# [<Extension>] method
12+
// Returns AsString result for scalar values; concatenates array elements
13+
// ============================================================
14+
15+
[<Test>]
16+
let ``InnerText returns string content for a String value`` () =
17+
JsonValue.String("hello world").InnerText() |> should equal "hello world"
18+
19+
[<Test>]
20+
let ``InnerText returns empty string for Null`` () =
21+
JsonValue.Null.InnerText() |> should equal ""
22+
23+
[<Test>]
24+
let ``InnerText returns string representation for Boolean true`` () =
25+
JsonValue.Boolean(true).InnerText() |> should equal "true"
26+
27+
[<Test>]
28+
let ``InnerText returns string representation for Boolean false`` () =
29+
JsonValue.Boolean(false).InnerText() |> should equal "false"
30+
31+
[<Test>]
32+
let ``InnerText returns string representation for Number`` () =
33+
JsonValue.Number(42M).InnerText() |> should equal "42"
34+
35+
[<Test>]
36+
let ``InnerText returns string representation for Float`` () =
37+
JsonValue.Float(3.14).InnerText() |> should startWith "3.14"
38+
39+
[<Test>]
40+
let ``InnerText concatenates string elements of an array`` () =
41+
let json = JsonValue.Parse """["foo","bar","baz"]"""
42+
json.InnerText() |> should equal "foobarbaz"
43+
44+
[<Test>]
45+
let ``InnerText returns empty string for an empty array`` () =
46+
JsonValue.Array([||]).InnerText() |> should equal ""
47+
48+
[<Test>]
49+
let ``InnerText includes numeric elements in a mixed array`` () =
50+
let json = JsonValue.Parse """["hello", 42, "world"]"""
51+
json.InnerText() |> should equal "hello42world"
52+
53+
[<Test>]
54+
let ``InnerText returns empty string for a record object`` () =
55+
let json = JsonValue.Parse """{"key":"value"}"""
56+
json.InnerText() |> should equal ""
57+
58+
[<Test>]
59+
let ``InnerText handles Unicode strings correctly`` () =
60+
JsonValue.String("こんにちは").InnerText() |> should equal "こんにちは"
61+
62+
[<Test>]
63+
let ``InnerText handles empty string`` () =
64+
JsonValue.String("").InnerText() |> should equal ""
65+
66+
[<Test>]
67+
let ``InnerText concatenates array of strings`` () =
68+
let json = JsonValue.Parse """["a","b","c","d","e"]"""
69+
json.InnerText() |> should equal "abcde"
70+
71+
// ============================================================
72+
// JsonValue.Properties — F# augmentation member
73+
// ============================================================
74+
75+
[<Test>]
76+
let ``Properties returns all key-value pairs for a Record`` () =
77+
let json = JsonValue.Parse """{"x":1,"y":2,"z":3}"""
78+
let props = json.Properties
79+
props |> should haveLength 3
80+
props |> Array.map fst |> should equal [| "x"; "y"; "z" |]
81+
82+
[<Test>]
83+
let ``Properties returns empty array for an empty record`` () =
84+
JsonValue.Record([||]).Properties |> should haveLength 0
85+
86+
[<Test>]
87+
let ``Properties returns empty array for a non-record value`` () =
88+
JsonValue.String("text").Properties |> should haveLength 0
89+
JsonValue.Array([||]).Properties |> should haveLength 0
90+
JsonValue.Null.Properties |> should haveLength 0
91+
JsonValue.Boolean(false).Properties |> should haveLength 0
92+
93+
// ============================================================
94+
// AsGuid — additional edge cases
95+
// ============================================================
96+
97+
[<Test>]
98+
let ``AsGuid parses lowercase GUID string`` () =
99+
let j = JsonValue.Parse """{"id":"550e8400-e29b-41d4-a716-446655440000"}"""
100+
j?id.AsGuid() |> should equal (Guid.Parse "550e8400-e29b-41d4-a716-446655440000")
101+
102+
[<Test>]
103+
let ``AsGuid parses uppercase GUID string`` () =
104+
let j = JsonValue.Parse """{"id":"550E8400-E29B-41D4-A716-446655440000"}"""
105+
j?id.AsGuid() |> should equal (Guid.Parse "550E8400-E29B-41D4-A716-446655440000")
106+
107+
[<Test>]
108+
let ``AsGuid throws for invalid GUID string`` () =
109+
let j = JsonValue.Parse """{"id":"not-a-guid"}"""
110+
(fun () -> j?id.AsGuid() |> ignore) |> should throw typeof<Exception>
111+
112+
// ============================================================
113+
// AsBoolean — string-to-bool conversion cases
114+
// ============================================================
115+
116+
[<Test>]
117+
let ``AsBoolean parses true string`` () =
118+
let j = JsonValue.Parse """{"flag":"true"}"""
119+
j?flag.AsBoolean() |> should equal true
120+
121+
[<Test>]
122+
let ``AsBoolean parses false string`` () =
123+
let j = JsonValue.Parse """{"flag":"false"}"""
124+
j?flag.AsBoolean() |> should equal false
125+
126+
[<Test>]
127+
let ``AsBoolean parses yes/no strings`` () =
128+
let j = JsonValue.Parse """{"a":"yes","b":"no"}"""
129+
j?a.AsBoolean() |> should equal true
130+
j?b.AsBoolean() |> should equal false
131+
132+
[<Test>]
133+
let ``AsBoolean parses 1/0 strings`` () =
134+
let j = JsonValue.Parse """{"a":"1","b":"0"}"""
135+
j?a.AsBoolean() |> should equal true
136+
j?b.AsBoolean() |> should equal false
137+
138+
[<Test>]
139+
let ``AsBoolean throws for invalid boolean string`` () =
140+
let j = JsonValue.Parse """{"flag":"maybe"}"""
141+
(fun () -> j?flag.AsBoolean() |> ignore) |> should throw typeof<Exception>
142+
143+
// ============================================================
144+
// AsTimeSpan — additional edge cases
145+
// ============================================================
146+
147+
[<Test>]
148+
let ``AsTimeSpan throws for non-timespan value`` () =
149+
let j = JsonValue.Parse """{"d":"not-a-timespan"}"""
150+
(fun () -> j?d.AsTimeSpan() |> ignore) |> should throw typeof<Exception>
151+
152+
[<Test>]
153+
let ``AsTimeSpan parses zero duration`` () =
154+
// Uses .NET TimeSpan.Parse format: "0:00:00"
155+
let j = JsonValue.Parse """{"d":"0:00:00"}"""
156+
j?d.AsTimeSpan() |> should equal TimeSpan.Zero
157+
158+
// ============================================================
159+
// Dynamic operator (?) edge cases
160+
// ============================================================
161+
162+
[<Test>]
163+
let ``Dynamic operator accesses nested string property`` () =
164+
let j = JsonValue.Parse """{"person":{"name":"Alice"}}"""
165+
j?person?name.AsString() |> should equal "Alice"
166+
167+
[<Test>]
168+
let ``Dynamic operator throws for missing property`` () =
169+
let j = JsonValue.Parse """{"x":1}"""
170+
(fun () -> j?missing |> ignore) |> should throw typeof<Exception>

0 commit comments

Comments
 (0)