@@ -7,6 +7,8 @@ open System.IO
77open System.Xml .Linq
88open System.Reflection
99open FSharp.Data .Runtime .BaseTypes
10+ open FSharp.Data .Runtime
11+ open FSharp.Data
1012
1113// These tests use reflection to test the XmlElement methods that are marked as "generated code only"
1214// This approach allows us to test the functionality while respecting the compiler constraints
@@ -64,4 +66,296 @@ let ``XmlElement _Print property handles short strings`` () =
6466 let printProperty = typeof< XmlElement>. GetProperty( " _Print" )
6567 let printed = printProperty.GetValue( xmlElement) :?> string
6668 printed |> should not' ( endWith " ..." )
67- printed |> should contain " short"
69+ printed |> should contain " short"
70+
71+ // Helper: wrap an XElement in XmlElement
72+ let private mkXml ( xelem : XElement ) : XmlElement = { XElement = xelem }
73+
74+ // ─── XmlRuntime.TryGetValue ────────────────────────────────────────────────
75+
76+ [<Test>]
77+ let ``TryGetValue returns None for element with no text content`` () =
78+ let xml = mkXml ( XElement( XName.Get( " empty" )))
79+ XmlRuntime.TryGetValue( xml) |> should equal None
80+
81+ [<Test>]
82+ let ``TryGetValue returns Some for element with text content`` () =
83+ let xml = mkXml ( XElement( XName.Get( " item" ), " hello" ))
84+ XmlRuntime.TryGetValue( xml) |> should equal ( Some " hello" )
85+
86+ [<Test>]
87+ let ``TryGetValue returns None for element whose value is empty string`` () =
88+ let xelem = XElement( XName.Get( " item" ))
89+ xelem.Value <- " "
90+ let xml = mkXml xelem
91+ XmlRuntime.TryGetValue( xml) |> should equal None
92+
93+ // ─── XmlRuntime.TryGetAttribute ───────────────────────────────────────────
94+
95+ [<Test>]
96+ let ``TryGetAttribute returns None when attribute is absent`` () =
97+ let xml = mkXml ( XElement( XName.Get( " item" )))
98+ XmlRuntime.TryGetAttribute( xml, " id" ) |> should equal None
99+
100+ [<Test>]
101+ let ``TryGetAttribute returns Some with attribute value when present`` () =
102+ let xelem = XElement( XName.Get( " item" ), XAttribute( XName.Get( " id" ), " 42" ))
103+ let xml = mkXml xelem
104+ XmlRuntime.TryGetAttribute( xml, " id" ) |> should equal ( Some " 42" )
105+
106+ [<Test>]
107+ let ``TryGetAttribute handles namespace - qualified attribute name`` () =
108+ let ns = XNamespace.Get( " http://example.com" )
109+ let xelem = XElement( XName.Get( " item" ), XAttribute( ns + " type" , " foo" ))
110+ let xml = mkXml xelem
111+ XmlRuntime.TryGetAttribute( xml, " {http://example.com}type" ) |> should equal ( Some " foo" )
112+
113+ // ─── XmlRuntime.GetChild ──────────────────────────────────────────────────
114+
115+ [<Test>]
116+ let ``GetChild returns the single matching child element`` () =
117+ let parent = XElement( XName.Get( " root" ), XElement( XName.Get( " child" ), " val" ))
118+ let xml = mkXml parent
119+ let child = XmlRuntime.GetChild( xml, " child" )
120+ child.XElement.Value |> should equal " val"
121+
122+ [<Test>]
123+ let ``GetChild throws when no matching child exists`` () =
124+ let xml = mkXml ( XElement( XName.Get( " root" )))
125+ ( fun () -> XmlRuntime.GetChild( xml, " missing" ) |> ignore)
126+ |> should throw typeof< Exception>
127+
128+ [<Test>]
129+ let ``GetChild throws when multiple matching children exist`` () =
130+ let parent =
131+ XElement(
132+ XName.Get( " root" ),
133+ XElement( XName.Get( " child" ), " 1" ),
134+ XElement( XName.Get( " child" ), " 2" )
135+ )
136+ let xml = mkXml parent
137+ ( fun () -> XmlRuntime.GetChild( xml, " child" ) |> ignore)
138+ |> should throw typeof< Exception>
139+
140+ // ─── XmlRuntime.ConvertArray ──────────────────────────────────────────────
141+
142+ [<Test>]
143+ let ``ConvertArray returns empty array when no matching children`` () =
144+ let xml = mkXml ( XElement( XName.Get( " root" )))
145+ let result = XmlRuntime.ConvertArray( xml, " item" , System.Func< XmlElement, string>( fun e -> e.XElement.Value))
146+ result |> should equal [||]
147+
148+ [<Test>]
149+ let ``ConvertArray returns converted values for all matching children`` () =
150+ let parent =
151+ XElement(
152+ XName.Get( " root" ),
153+ XElement( XName.Get( " item" ), " 1" ),
154+ XElement( XName.Get( " item" ), " 2" ),
155+ XElement( XName.Get( " item" ), " 3" )
156+ )
157+ let xml = mkXml parent
158+ let result = XmlRuntime.ConvertArray( xml, " item" , System.Func< XmlElement, int>( fun e -> int e.XElement.Value))
159+ result |> should equal [| 1 ; 2 ; 3 |]
160+
161+ [<Test>]
162+ let ``ConvertArray ignores children with different names`` () =
163+ let parent =
164+ XElement(
165+ XName.Get( " root" ),
166+ XElement( XName.Get( " item" ), " a" ),
167+ XElement( XName.Get( " other" ), " b" )
168+ )
169+ let xml = mkXml parent
170+ let result = XmlRuntime.ConvertArray( xml, " item" , System.Func< XmlElement, string>( fun e -> e.XElement.Value))
171+ result |> should equal [| " a" |]
172+
173+ // ─── XmlRuntime.ConvertOptional ───────────────────────────────────────────
174+
175+ [<Test>]
176+ let ``ConvertOptional returns None when no matching child exists`` () =
177+ let xml = mkXml ( XElement( XName.Get( " root" )))
178+ let result = XmlRuntime.ConvertOptional( xml, " opt" , System.Func< XmlElement, string>( fun e -> e.XElement.Value))
179+ result |> should equal None
180+
181+ [<Test>]
182+ let ``ConvertOptional returns Some when exactly one matching child exists`` () =
183+ let parent = XElement( XName.Get( " root" ), XElement( XName.Get( " opt" ), " 42" ))
184+ let xml = mkXml parent
185+ let result = XmlRuntime.ConvertOptional( xml, " opt" , System.Func< XmlElement, int>( fun e -> int e.XElement.Value))
186+ result |> should equal ( Some 42 )
187+
188+ [<Test>]
189+ let ``ConvertOptional throws when more than one matching child exists`` () =
190+ let parent =
191+ XElement(
192+ XName.Get( " root" ),
193+ XElement( XName.Get( " opt" ), " 1" ),
194+ XElement( XName.Get( " opt" ), " 2" )
195+ )
196+ let xml = mkXml parent
197+ ( fun () -> XmlRuntime.ConvertOptional( xml, " opt" , System.Func< XmlElement, string>( fun e -> e.XElement.Value)) |> ignore)
198+ |> should throw typeof< Exception>
199+
200+ // ─── XmlRuntime.ConvertOptional2 ──────────────────────────────────────────
201+
202+ [<Test>]
203+ let ``ConvertOptional2 returns None when no matching child`` () =
204+ let xml = mkXml ( XElement( XName.Get( " root" )))
205+ let result =
206+ XmlRuntime.ConvertOptional2( xml, " opt" , System.Func< XmlElement, string option>( fun e -> Some e.XElement.Value))
207+ result |> should equal None
208+
209+ [<Test>]
210+ let ``ConvertOptional2 returns None when child exists but inner function returns None`` () =
211+ let parent = XElement( XName.Get( " root" ), XElement( XName.Get( " opt" )))
212+ let xml = mkXml parent
213+ let result =
214+ XmlRuntime.ConvertOptional2( xml, " opt" , System.Func< XmlElement, string option>( fun _ -> None))
215+ result |> should equal None
216+
217+ [<Test>]
218+ let ``ConvertOptional2 returns Some when child exists and inner function returns Some`` () =
219+ let parent = XElement( XName.Get( " root" ), XElement( XName.Get( " opt" ), " hello" ))
220+ let xml = mkXml parent
221+ let result =
222+ XmlRuntime.ConvertOptional2( xml, " opt" , System.Func< XmlElement, string option>( fun e -> Some e.XElement.Value))
223+ result |> should equal ( Some " hello" )
224+
225+ // ─── XmlRuntime.ConvertAsName ─────────────────────────────────────────────
226+
227+ [<Test>]
228+ let ``ConvertAsName returns None when element name does not match`` () =
229+ let xml = mkXml ( XElement( XName.Get( " foo" )))
230+ let result =
231+ XmlRuntime.ConvertAsName( xml, " bar" , System.Func< XmlElement, string>( fun e -> e.XElement.Name.LocalName))
232+ result |> should equal None
233+
234+ [<Test>]
235+ let ``ConvertAsName returns Some when element name matches`` () =
236+ let xml = mkXml ( XElement( XName.Get( " foo" ), " data" ))
237+ let result =
238+ XmlRuntime.ConvertAsName( xml, " foo" , System.Func< XmlElement, string>( fun e -> e.XElement.Value))
239+ result |> should equal ( Some " data" )
240+
241+ // ─── XmlRuntime.GetJsonValue / TryGetJsonValue ────────────────────────────
242+
243+ [<Test>]
244+ let ``GetJsonValue parses embedded JSON string as JsonDocument`` () =
245+ let xml = mkXml ( XElement( XName.Get( " item" ), """ {"x":1}""" ))
246+ let doc = XmlRuntime.GetJsonValue( xml)
247+ doc.JsonValue.[ " x" ]. AsInteger() |> should equal 1
248+
249+ [<Test>]
250+ let ``GetJsonValue throws when element has no text content`` () =
251+ let xml = mkXml ( XElement( XName.Get( " item" )))
252+ ( fun () -> XmlRuntime.GetJsonValue( xml) |> ignore)
253+ |> should throw typeof< Exception>
254+
255+ [<Test>]
256+ let ``TryGetJsonValue returns None when element has no text content`` () =
257+ let xml = mkXml ( XElement( XName.Get( " item" )))
258+ XmlRuntime.TryGetJsonValue( xml) |> should equal None
259+
260+ [<Test>]
261+ let ``TryGetJsonValue returns None when element text is not valid JSON`` () =
262+ let xml = mkXml ( XElement( XName.Get( " item" ), " not-json!" ))
263+ XmlRuntime.TryGetJsonValue( xml) |> should equal None
264+
265+ [<Test>]
266+ let ``TryGetJsonValue returns Some for valid JSON content`` () =
267+ let xml = mkXml ( XElement( XName.Get( " item" ), " [1,2,3]" ))
268+ let result = XmlRuntime.TryGetJsonValue( xml)
269+ result |> should not' ( equal None)
270+ result.Value.JsonValue.AsArray() .Length |> should equal 3
271+
272+ // ─── XmlRuntime.CreateValue ───────────────────────────────────────────────
273+
274+ [<Test>]
275+ let ``CreateValue creates element with string value`` () =
276+ let xml = XmlRuntime.CreateValue( " greeting" , box " hello" , " " )
277+ xml.XElement.Name.LocalName |> should equal " greeting"
278+ xml.XElement.Value |> should equal " hello"
279+
280+ [<Test>]
281+ let ``CreateValue creates element with integer value`` () =
282+ let xml = XmlRuntime.CreateValue( " count" , box 42 , " " )
283+ xml.XElement.Name.LocalName |> should equal " count"
284+ xml.XElement.Value |> should equal " 42"
285+
286+ [<Test>]
287+ let ``CreateValue creates element with boolean true`` () =
288+ let xml = XmlRuntime.CreateValue( " flag" , box true , " " )
289+ xml.XElement.Value |> should equal " true"
290+
291+ [<Test>]
292+ let ``CreateValue creates element with boolean false`` () =
293+ let xml = XmlRuntime.CreateValue( " flag" , box false , " " )
294+ xml.XElement.Value |> should equal " false"
295+
296+ // ─── XmlRuntime.CreateRecord ──────────────────────────────────────────────
297+
298+ [<Test>]
299+ let ``CreateRecord creates element with no attributes or children`` () =
300+ let xml = XmlRuntime.CreateRecord( " record" , [||], [||], " " )
301+ xml.XElement.Name.LocalName |> should equal " record"
302+ xml.XElement.HasAttributes |> should equal false
303+ xml.XElement.HasElements |> should equal false
304+
305+ [<Test>]
306+ let ``CreateRecord creates element with attributes`` () =
307+ let xml = XmlRuntime.CreateRecord( " record" , [| " id" , box " 99" |], [||], " " )
308+ xml.XElement.Attribute( XName.Get( " id" )) .Value |> should equal " 99"
309+
310+ [<Test>]
311+ let ``CreateRecord creates element with text content via empty - string name`` () =
312+ let xml = XmlRuntime.CreateRecord( " record" , [||], [| " " , box " body text" |], " " )
313+ xml.XElement.Value |> should equal " body text"
314+
315+ [<Test>]
316+ let ``CreateRecord creates element with named child element`` () =
317+ let xml = XmlRuntime.CreateRecord( " root" , [||], [| " child" , box " 42" |], " " )
318+ let child = xml.XElement.Element( XName.Get( " child" ))
319+ child |> should not' ( equal null )
320+ child.Value |> should equal " 42"
321+
322+ [<Test>]
323+ let ``CreateRecord serialises DateTime value as ISO date string`` () =
324+ let dt = DateTime( 2024 , 3 , 15 )
325+ let xml = XmlRuntime.CreateValue( " date" , box dt, " " )
326+ xml.XElement.Value |> should equal " 2024-03-15"
327+
328+ [<Test>]
329+ let ``CreateRecord serialises DateTime with time as ISO - 8601 string`` () =
330+ let dt = DateTime( 2024 , 3 , 15 , 10 , 30 , 0 )
331+ let xml = XmlRuntime.CreateValue( " ts" , box dt, " " )
332+ xml.XElement.Value |> should startWith " 2024-03-15T"
333+
334+ [<Test>]
335+ let ``CreateRecord handles optional string element — Some value`` () =
336+ let xml = XmlRuntime.CreateRecord( " root" , [||], [| " opt" , box ( Some " present" ) |], " " )
337+ xml.XElement.Element( XName.Get( " opt" )) .Value |> should equal " present"
338+
339+ [<Test>]
340+ let ``CreateRecord handles optional string element — None omits child`` () =
341+ let xml = XmlRuntime.CreateRecord( " root" , [||], [| " opt" , box ( None: string option) |], " " )
342+ xml.XElement.Element( XName.Get( " opt" )) |> should equal null
343+
344+ // ─── XmlRuntime — path navigation via pipe-separated names ────────────────
345+
346+ [<Test>]
347+ let ``ConvertArray navigates path with pipe - separated names`` () =
348+ // <root><parent><child>A</child><child>B</child></parent></root>
349+ let grandchild1 = XElement( XName.Get( " child" ), " A" )
350+ let grandchild2 = XElement( XName.Get( " child" ), " B" )
351+ let parentElem = XElement( XName.Get( " parent" ), grandchild1, grandchild2)
352+ let root = XElement( XName.Get( " root" ), parentElem)
353+ let xml = mkXml root
354+ let result = XmlRuntime.ConvertArray( xml, " parent|child" , System.Func< XmlElement, string>( fun e -> e.XElement.Value))
355+ result |> should equal [| " A" ; " B" |]
356+
357+ [<Test>]
358+ let ``ConvertArray returns empty array when intermediate path element is missing`` () =
359+ let xml = mkXml ( XElement( XName.Get( " root" )))
360+ let result = XmlRuntime.ConvertArray( xml, " missing|child" , System.Func< XmlElement, string>( fun e -> e.XElement.Value))
361+ result |> should equal [||]
0 commit comments