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 dbbe3e5fd..ab66f9ed0 100644 --- a/tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj +++ b/tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj @@ -35,6 +35,7 @@ + diff --git a/tests/FSharp.Data.Core.Tests/JsonValueOptionExtensions.fs b/tests/FSharp.Data.Core.Tests/JsonValueOptionExtensions.fs new file mode 100644 index 000000000..88363edba --- /dev/null +++ b/tests/FSharp.Data.Core.Tests/JsonValueOptionExtensions.fs @@ -0,0 +1,170 @@ +module FSharp.Data.Tests.JsonValueInnerTextAndExtensions + +open NUnit.Framework +open FsUnit +open System +open System.Globalization +open FSharp.Data +open FSharp.Data.JsonExtensions + +// ============================================================ +// JsonExtensions.InnerText — C# [] method +// Returns AsString result for scalar values; concatenates array elements +// ============================================================ + +[] +let ``InnerText returns string content for a String value`` () = + JsonValue.String("hello world").InnerText() |> should equal "hello world" + +[] +let ``InnerText returns empty string for Null`` () = + JsonValue.Null.InnerText() |> should equal "" + +[] +let ``InnerText returns string representation for Boolean true`` () = + JsonValue.Boolean(true).InnerText() |> should equal "true" + +[] +let ``InnerText returns string representation for Boolean false`` () = + JsonValue.Boolean(false).InnerText() |> should equal "false" + +[] +let ``InnerText returns string representation for Number`` () = + JsonValue.Number(42M).InnerText() |> should equal "42" + +[] +let ``InnerText returns string representation for Float`` () = + JsonValue.Float(3.14).InnerText() |> should startWith "3.14" + +[] +let ``InnerText concatenates string elements of an array`` () = + let json = JsonValue.Parse """["foo","bar","baz"]""" + json.InnerText() |> should equal "foobarbaz" + +[] +let ``InnerText returns empty string for an empty array`` () = + JsonValue.Array([||]).InnerText() |> should equal "" + +[] +let ``InnerText includes numeric elements in a mixed array`` () = + let json = JsonValue.Parse """["hello", 42, "world"]""" + json.InnerText() |> should equal "hello42world" + +[] +let ``InnerText returns empty string for a record object`` () = + let json = JsonValue.Parse """{"key":"value"}""" + json.InnerText() |> should equal "" + +[] +let ``InnerText handles Unicode strings correctly`` () = + JsonValue.String("こんにちは").InnerText() |> should equal "こんにちは" + +[] +let ``InnerText handles empty string`` () = + JsonValue.String("").InnerText() |> should equal "" + +[] +let ``InnerText concatenates array of strings`` () = + let json = JsonValue.Parse """["a","b","c","d","e"]""" + json.InnerText() |> should equal "abcde" + +// ============================================================ +// JsonValue.Properties — F# augmentation member +// ============================================================ + +[] +let ``Properties returns all key-value pairs for a Record`` () = + let json = JsonValue.Parse """{"x":1,"y":2,"z":3}""" + let props = json.Properties + props |> should haveLength 3 + props |> Array.map fst |> should equal [| "x"; "y"; "z" |] + +[] +let ``Properties returns empty array for an empty record`` () = + JsonValue.Record([||]).Properties |> should haveLength 0 + +[] +let ``Properties returns empty array for a non-record value`` () = + JsonValue.String("text").Properties |> should haveLength 0 + JsonValue.Array([||]).Properties |> should haveLength 0 + JsonValue.Null.Properties |> should haveLength 0 + JsonValue.Boolean(false).Properties |> should haveLength 0 + +// ============================================================ +// AsGuid — additional edge cases +// ============================================================ + +[] +let ``AsGuid parses lowercase GUID string`` () = + let j = JsonValue.Parse """{"id":"550e8400-e29b-41d4-a716-446655440000"}""" + j?id.AsGuid() |> should equal (Guid.Parse "550e8400-e29b-41d4-a716-446655440000") + +[] +let ``AsGuid parses uppercase GUID string`` () = + let j = JsonValue.Parse """{"id":"550E8400-E29B-41D4-A716-446655440000"}""" + j?id.AsGuid() |> should equal (Guid.Parse "550E8400-E29B-41D4-A716-446655440000") + +[] +let ``AsGuid throws for invalid GUID string`` () = + let j = JsonValue.Parse """{"id":"not-a-guid"}""" + (fun () -> j?id.AsGuid() |> ignore) |> should throw typeof + +// ============================================================ +// AsBoolean — string-to-bool conversion cases +// ============================================================ + +[] +let ``AsBoolean parses true string`` () = + let j = JsonValue.Parse """{"flag":"true"}""" + j?flag.AsBoolean() |> should equal true + +[] +let ``AsBoolean parses false string`` () = + let j = JsonValue.Parse """{"flag":"false"}""" + j?flag.AsBoolean() |> should equal false + +[] +let ``AsBoolean parses yes/no strings`` () = + let j = JsonValue.Parse """{"a":"yes","b":"no"}""" + j?a.AsBoolean() |> should equal true + j?b.AsBoolean() |> should equal false + +[] +let ``AsBoolean parses 1/0 strings`` () = + let j = JsonValue.Parse """{"a":"1","b":"0"}""" + j?a.AsBoolean() |> should equal true + j?b.AsBoolean() |> should equal false + +[] +let ``AsBoolean throws for invalid boolean string`` () = + let j = JsonValue.Parse """{"flag":"maybe"}""" + (fun () -> j?flag.AsBoolean() |> ignore) |> should throw typeof + +// ============================================================ +// AsTimeSpan — additional edge cases +// ============================================================ + +[] +let ``AsTimeSpan throws for non-timespan value`` () = + let j = JsonValue.Parse """{"d":"not-a-timespan"}""" + (fun () -> j?d.AsTimeSpan() |> ignore) |> should throw typeof + +[] +let ``AsTimeSpan parses zero duration`` () = + // Uses .NET TimeSpan.Parse format: "0:00:00" + let j = JsonValue.Parse """{"d":"0:00:00"}""" + j?d.AsTimeSpan() |> should equal TimeSpan.Zero + +// ============================================================ +// Dynamic operator (?) edge cases +// ============================================================ + +[] +let ``Dynamic operator accesses nested string property`` () = + let j = JsonValue.Parse """{"person":{"name":"Alice"}}""" + j?person?name.AsString() |> should equal "Alice" + +[] +let ``Dynamic operator throws for missing property`` () = + let j = JsonValue.Parse """{"x":1}""" + (fun () -> j?missing |> ignore) |> should throw typeof