1+ module FSharp.Data.Tests.TextRuntime
2+
3+ open NUnit.Framework
4+ open FsUnit
5+ open System
6+ open System.Globalization
7+ open FSharp.Data
8+ open FSharp.Data .Runtime
9+
10+ [<Test>]
11+ let ``GetCulture returns InvariantCulture for null or empty`` () =
12+ TextRuntime.GetCulture( null ) |> should equal CultureInfo.InvariantCulture
13+ TextRuntime.GetCulture( " " ) |> should equal CultureInfo.InvariantCulture
14+ TextRuntime.GetCulture( " " ) |> should equal CultureInfo.InvariantCulture
15+
16+ [<Test>]
17+ let ``GetCulture returns correct culture for valid string`` () =
18+ let culture = TextRuntime.GetCulture( " en-US" )
19+ culture.Name |> should equal " en-US"
20+
21+ let culture2 = TextRuntime.GetCulture( " de-DE" )
22+ culture2.Name |> should equal " de-DE"
23+
24+ [<Test>]
25+ let ``GetCulture caches cultures correctly`` () =
26+ let culture1 = TextRuntime.GetCulture( " fr-FR" )
27+ let culture2 = TextRuntime.GetCulture( " fr-FR" )
28+ // Should be same reference (cached)
29+ Object.ReferenceEquals( culture1, culture2) |> should equal true
30+
31+ [<Test>]
32+ let ``GetMissingValues returns default for null or empty`` () =
33+ let defaultValues = TextRuntime.GetMissingValues( null )
34+ defaultValues |> should equal TextConversions.DefaultMissingValues
35+
36+ let emptyValues = TextRuntime.GetMissingValues( " " )
37+ emptyValues |> should equal TextConversions.DefaultMissingValues
38+
39+ let whitespaceValues = TextRuntime.GetMissingValues( " " )
40+ whitespaceValues |> should equal TextConversions.DefaultMissingValues
41+
42+ [<Test>]
43+ let ``GetMissingValues parses comma - separated values`` () =
44+ let values = TextRuntime.GetMissingValues( " NA,NULL,#N/A" )
45+ values |> should equal [| " NA" ; " NULL" ; " #N/A" |]
46+
47+ let valuesWithSpaces = TextRuntime.GetMissingValues( " N/A, null , missing" )
48+ valuesWithSpaces |> should equal [| " N/A" ; " null " ; " missing" |]
49+
50+ [<Test>]
51+ let ``ConvertString returns the option as - is`` () =
52+ TextRuntime.ConvertString( Some " test" ) |> should equal ( Some " test" )
53+ TextRuntime.ConvertString( None) |> should equal None
54+
55+ [<Test>]
56+ let ``ConvertInteger with culture`` () =
57+ TextRuntime.ConvertInteger( " en-US" , Some " 123" ) |> should equal ( Some 123 )
58+ TextRuntime.ConvertInteger( " de-DE" , Some " 456" ) |> should equal ( Some 456 )
59+ TextRuntime.ConvertInteger( " en-US" , Some " invalid" ) |> should equal None
60+ TextRuntime.ConvertInteger( " en-US" , None) |> should equal None
61+
62+ [<Test>]
63+ let ``ConvertInteger64 with culture`` () =
64+ TextRuntime.ConvertInteger64( " en-US" , Some " 9223372036854775807" ) |> should equal ( Some 9223372036854775807 L)
65+ TextRuntime.ConvertInteger64( " en-US" , Some " invalid" ) |> should equal None
66+ TextRuntime.ConvertInteger64( " en-US" , None) |> should equal None
67+
68+ [<Test>]
69+ let ``ConvertDecimal with culture`` () =
70+ TextRuntime.ConvertDecimal( " en-US" , Some " 123.45" ) |> should equal ( Some 123.45 m)
71+ TextRuntime.ConvertDecimal( " en-US" , Some " 67.89" ) |> should equal ( Some 67.89 m)
72+ TextRuntime.ConvertDecimal( " en-US" , Some " invalid" ) |> should equal None
73+ TextRuntime.ConvertDecimal( " en-US" , None) |> should equal None
74+
75+ [<Test>]
76+ let ``ConvertFloat with culture and missing values`` () =
77+ TextRuntime.ConvertFloat( " en-US" , " NA,NULL" , Some " 123.45" ) |> should equal ( Some 123.45 )
78+ TextRuntime.ConvertFloat( " en-US" , " NA,NULL" , Some " NA" ) |> should equal None
79+ TextRuntime.ConvertFloat( " en-US" , " NA,NULL" , Some " NULL" ) |> should equal None
80+ TextRuntime.ConvertFloat( " en-US" , " NA,NULL" , Some " invalid" ) |> should equal None
81+ TextRuntime.ConvertFloat( " en-US" , " NA,NULL" , None) |> should equal None
82+
83+ [<Test>]
84+ let ``ConvertBoolean`` () =
85+ TextRuntime.ConvertBoolean( Some " true" ) |> should equal ( Some true )
86+ TextRuntime.ConvertBoolean( Some " false" ) |> should equal ( Some false )
87+ TextRuntime.ConvertBoolean( Some " 1" ) |> should equal ( Some true )
88+ TextRuntime.ConvertBoolean( Some " 0" ) |> should equal ( Some false )
89+ TextRuntime.ConvertBoolean( Some " invalid" ) |> should equal None
90+ TextRuntime.ConvertBoolean( None) |> should equal None
91+
92+ [<Test>]
93+ let ``ConvertDateTime with culture`` () =
94+ let result = TextRuntime.ConvertDateTime( " en-US" , Some " 2023-12-25" )
95+ result |> should not' ( equal None)
96+ result.Value.Year |> should equal 2023
97+ result.Value.Month |> should equal 12
98+ result.Value.Day |> should equal 25
99+
100+ TextRuntime.ConvertDateTime( " en-US" , Some " invalid" ) |> should equal None
101+ TextRuntime.ConvertDateTime( " en-US" , None) |> should equal None
102+
103+ [<Test>]
104+ let ``ConvertDateTimeOffset with culture`` () =
105+ let result = TextRuntime.ConvertDateTimeOffset( " en-US" , Some " 2023-12-25T10:30:00+02:00" )
106+ result |> should not' ( equal None)
107+ result.Value.Year |> should equal 2023
108+ result.Value.Offset.Hours |> should equal 2
109+
110+ TextRuntime.ConvertDateTimeOffset( " en-US" , Some " invalid" ) |> should equal None
111+ TextRuntime.ConvertDateTimeOffset( " en-US" , None) |> should equal None
112+
113+ [<Test>]
114+ let ``ConvertTimeSpan with culture`` () =
115+ let result = TextRuntime.ConvertTimeSpan( " en-US" , Some " 02:30:45" )
116+ result |> should not' ( equal None)
117+ result.Value.Hours |> should equal 2
118+ result.Value.Minutes |> should equal 30
119+ result.Value.Seconds |> should equal 45
120+
121+ TextRuntime.ConvertTimeSpan( " en-US" , Some " invalid" ) |> should equal None
122+ TextRuntime.ConvertTimeSpan( " en-US" , None) |> should equal None
123+
124+ [<Test>]
125+ let ``ConvertGuid`` () =
126+ let guid = Guid.NewGuid()
127+ let result = TextRuntime.ConvertGuid( Some ( guid.ToString()))
128+ result |> should equal ( Some guid)
129+
130+ TextRuntime.ConvertGuid( Some " invalid" ) |> should equal None
131+ TextRuntime.ConvertGuid( None) |> should equal None
132+
133+ [<Test>]
134+ let ``ConvertStringBack`` () =
135+ TextRuntime.ConvertStringBack( Some " test" ) |> should equal " test"
136+ TextRuntime.ConvertStringBack( None) |> should equal " "
137+
138+ [<Test>]
139+ let ``ConvertIntegerBack with culture`` () =
140+ TextRuntime.ConvertIntegerBack( " en-US" , Some 1234 ) |> should equal " 1234"
141+ TextRuntime.ConvertIntegerBack( " de-DE" , Some 1234 ) |> should equal " 1234"
142+ TextRuntime.ConvertIntegerBack( " en-US" , None) |> should equal " "
143+
144+ [<Test>]
145+ let ``ConvertInteger64Back with culture`` () =
146+ TextRuntime.ConvertInteger64Back( " en-US" , Some 1234567890123456789 L) |> should equal " 1234567890123456789"
147+ TextRuntime.ConvertInteger64Back( " en-US" , None) |> should equal " "
148+
149+ [<Test>]
150+ let ``ConvertDecimalBack with culture`` () =
151+ let result = TextRuntime.ConvertDecimalBack( " en-US" , Some 123.45 m)
152+ result |> should equal " 123.45"
153+ TextRuntime.ConvertDecimalBack( " en-US" , None) |> should equal " "
154+
155+ [<Test>]
156+ let ``ConvertFloatBack with NaN and missing values`` () =
157+ TextRuntime.ConvertFloatBack( " en-US" , " NA,NULL" , Some 123.45 ) |> should equal " 123.45"
158+ TextRuntime.ConvertFloatBack( " en-US" , " NA,NULL" , Some Double.NaN) |> should equal " NA"
159+ TextRuntime.ConvertFloatBack( " en-US" , " " , Some Double.NaN) |> should equal " NaN"
160+ TextRuntime.ConvertFloatBack( " en-US" , " NA,NULL" , None) |> should equal " "
161+
162+ [<Test>]
163+ let ``ConvertBooleanBack with different formats`` () =
164+ TextRuntime.ConvertBooleanBack( Some true , false ) |> should equal " true"
165+ TextRuntime.ConvertBooleanBack( Some false , false ) |> should equal " false"
166+ TextRuntime.ConvertBooleanBack( Some true , true ) |> should equal " 1"
167+ TextRuntime.ConvertBooleanBack( Some false , true ) |> should equal " 0"
168+ TextRuntime.ConvertBooleanBack( None, false ) |> should equal " "
169+ TextRuntime.ConvertBooleanBack( None, true ) |> should equal " "
170+
171+ [<Test>]
172+ let ``ConvertDateTimeBack with ISO format`` () =
173+ let dateTime = DateTime( 2023 , 12 , 25 , 15 , 30 , 45 , DateTimeKind.Utc)
174+ let result = TextRuntime.ConvertDateTimeBack( " en-US" , Some dateTime)
175+ result |> should startWith " 2023-12-25T15:30:45"
176+ TextRuntime.ConvertDateTimeBack( " en-US" , None) |> should equal " "
177+
178+ [<Test>]
179+ let ``ConvertDateTimeOffsetBack with ISO format`` () =
180+ let dto = DateTimeOffset( 2023 , 12 , 25 , 15 , 30 , 45 , TimeSpan.FromHours( 2 ))
181+ let result = TextRuntime.ConvertDateTimeOffsetBack( " en-US" , Some dto)
182+ result |> should contain " 2023-12-25T15:30:45"
183+ result |> should contain " +02:00"
184+ TextRuntime.ConvertDateTimeOffsetBack( " en-US" , None) |> should equal " "
185+
186+ [<Test>]
187+ let ``ConvertTimeSpanBack`` () =
188+ let timeSpan = TimeSpan( 2 , 30 , 45 )
189+ let result = TextRuntime.ConvertTimeSpanBack( " en-US" , Some timeSpan)
190+ result |> should equal " 2:30:45"
191+ TextRuntime.ConvertTimeSpanBack( " en-US" , None) |> should equal " "
192+
193+ [<Test>]
194+ let ``ConvertGuidBack`` () =
195+ let guid = Guid.NewGuid()
196+ TextRuntime.ConvertGuidBack( Some guid) |> should equal ( guid.ToString())
197+ TextRuntime.ConvertGuidBack( None) |> should equal " "
198+
199+ [<Test>]
200+ let ``GetNonOptionalValue with Some values`` () =
201+ TextRuntime.GetNonOptionalValue( " test" , Some " value" , None) |> should equal " value"
202+ TextRuntime.GetNonOptionalValue( " test" , Some 42 , None) |> should equal 42
203+
204+ [<Test>]
205+ let ``GetNonOptionalValue with None for string returns empty`` () =
206+ TextRuntime.GetNonOptionalValue< string>( " test" , None, None) |> should equal " "
207+
208+ [<Test>]
209+ let ``GetNonOptionalValue with None for float returns NaN`` () =
210+ let result = TextRuntime.GetNonOptionalValue< float>( " test" , None, None)
211+ Double.IsNaN( result) |> should equal true
212+
213+ [<Test>]
214+ let ``GetNonOptionalValue throws for missing non - special types`` () =
215+ ( fun () -> TextRuntime.GetNonOptionalValue< int>( " test" , None, None) |> ignore)
216+ |> should throw typeof< Exception>
217+
218+ [<Test>]
219+ let ``GetNonOptionalValue with original value provides helpful error`` () =
220+ ( fun () -> TextRuntime.GetNonOptionalValue< int>( " test" , None, Some " invalid" ) |> ignore)
221+ |> should throw typeof< Exception>
222+
223+ [<Test>]
224+ let ``OptionToNullable conversion`` () =
225+ let nullable = TextRuntime.OptionToNullable( Some 42 )
226+ nullable.HasValue |> should equal true
227+ nullable.Value |> should equal 42
228+
229+ let nullableNone = TextRuntime.OptionToNullable< int>( None)
230+ nullableNone.HasValue |> should equal false
231+
232+ [<Test>]
233+ let ``NullableToOption conversion`` () =
234+ let nullable = Nullable( 42 )
235+ TextRuntime.NullableToOption( nullable) |> should equal ( Some 42 )
236+
237+ let nullableEmpty = Nullable< int>()
238+ TextRuntime.NullableToOption( nullableEmpty) |> should equal None
239+
240+ [<Test>]
241+ let ``AsyncMap transformation`` () =
242+ async {
243+ let valueAsync = async { return 5 }
244+ let mapping = Func< int, string>( fun x -> sprintf " Value: %d " x)
245+ let! result = TextRuntime.AsyncMap( valueAsync, mapping)
246+ result |> should equal " Value: 5"
247+ } |> Async.RunSynchronously
0 commit comments