@@ -114,4 +114,248 @@ GHI;3
114114 [| " QUOTED" ; " 4" |]
115115 [| " " ; " 5" |] |]
116116
117- actual |> should equal expected
117+ actual |> should equal expected
118+
119+ // --------------------------------------------------------------------------------------
120+ // Tests for CSV Core components that need better coverage
121+ // --------------------------------------------------------------------------------------
122+
123+ open FSharp.Data
124+
125+ // CsvFile tests
126+ [<Test>]
127+ let ``CsvFile.Parse can parse simple CSV`` () =
128+ let csv = CsvFile.Parse( " name,age\n Alice,25\n Bob,30" )
129+ csv.Headers |> should equal ( Some [| " name" ; " age" |])
130+ csv.Rows |> Seq.length |> should equal 2
131+ let firstRow = csv.Rows |> Seq.head
132+ firstRow.[ " name" ] |> should equal " Alice"
133+ firstRow.[ " age" ] |> should equal " 25"
134+
135+ [<Test>]
136+ let ``CsvFile.Parse with custom separators`` () =
137+ let csv = CsvFile.Parse( " name;age\n Alice;25\n Bob;30" , separators= " ;" )
138+ csv.Headers |> should equal ( Some [| " name" ; " age" |])
139+ let firstRow = csv.Rows |> Seq.head
140+ firstRow.[ " name" ] |> should equal " Alice"
141+
142+ [<Test>]
143+ let ``CsvFile.Parse with custom quote character`` () =
144+ let csv = CsvFile.Parse( " name,value\n Alice,'Hello, World'" , quote= ''' )
145+ let firstRow = csv.Rows |> Seq.head
146+ firstRow.[ " value" ] |> should equal " Hello, World"
147+
148+ [<Test>]
149+ let ``CsvFile.Parse without headers`` () =
150+ let csv = CsvFile.Parse( " Alice,25\n Bob,30" , hasHeaders= false )
151+ csv.Headers |> should equal None
152+ let firstRow = csv.Rows |> Seq.head
153+ firstRow.[ 0 ] |> should equal " Alice"
154+ firstRow.[ 1 ] |> should equal " 25"
155+
156+ [<Test>]
157+ let ``CsvFile.Parse with skipRows`` () =
158+ let csv = CsvFile.Parse( " comment line\n name,age\n Alice,25" , skipRows= 1 )
159+ csv.Headers |> should equal ( Some [| " name" ; " age" |])
160+ let firstRow = csv.Rows |> Seq.head
161+ firstRow.[ " name" ] |> should equal " Alice"
162+
163+ [<Test>]
164+ let ``CsvFile.Parse with ignoreErrors true handles malformed rows`` () =
165+ let csv = CsvFile.Parse( " name,age\n Alice,25\n Bob\n Charlie,35" , ignoreErrors= true )
166+ csv.Rows |> Seq.length |> should equal 2 // Malformed "Bob" row should be ignored
167+
168+ [<Test>]
169+ let ``CsvFile column indexing works correctly`` () =
170+ let csv = CsvFile.Parse( " first,second,third\n A,B,C" )
171+ let firstRow = csv.Rows |> Seq.head
172+ firstRow.[ " first" ] |> should equal " A"
173+ firstRow.[ " second" ] |> should equal " B"
174+ firstRow.[ " third" ] |> should equal " C"
175+
176+ [<Test>]
177+ let ``CsvFile GetColumnIndex returns correct index`` () =
178+ let csv = CsvFile.Parse( " first,second,third\n A,B,C" )
179+ csv.GetColumnIndex( " first" ) |> should equal 0
180+ csv.GetColumnIndex( " second" ) |> should equal 1
181+ csv.GetColumnIndex( " third" ) |> should equal 2
182+
183+ [<Test>]
184+ let ``CsvFile TryGetColumnIndex returns Some for valid column`` () =
185+ let csv = CsvFile.Parse( " first,second\n A,B" )
186+ csv.TryGetColumnIndex( " first" ) |> should equal ( Some 0 )
187+ csv.TryGetColumnIndex( " missing" ) |> should equal None
188+
189+ [<Test>]
190+ let ``CsvFile GetColumnIndex throws for invalid column`` () =
191+ let csv = CsvFile.Parse( " first,second\n A,B" )
192+ ( fun () -> csv.GetColumnIndex( " missing" ) |> ignore) |> should throw typeof< System.Collections.Generic.KeyNotFoundException>
193+
194+ // CsvExtensions edge case tests
195+ [<Test>]
196+ let ``AsInteger throws for invalid string`` () =
197+ ( fun () -> " not_a_number" .AsInteger() |> ignore) |> should throw typeof< System.Exception>
198+
199+ [<Test>]
200+ let ``AsInteger64 throws for invalid string`` () =
201+ ( fun () -> " not_a_number" .AsInteger64() |> ignore) |> should throw typeof< System.Exception>
202+
203+ [<Test>]
204+ let ``AsDecimal throws for invalid string`` () =
205+ ( fun () -> " not_a_decimal" .AsDecimal() |> ignore) |> should throw typeof< System.Exception>
206+
207+ [<Test>]
208+ let ``AsFloat throws for invalid string`` () =
209+ ( fun () -> " not_a_float" .AsFloat() |> ignore) |> should throw typeof< System.Exception>
210+
211+ [<Test>]
212+ let ``AsBoolean throws for invalid string`` () =
213+ ( fun () -> " not_a_bool" .AsBoolean() |> ignore) |> should throw typeof< System.Exception>
214+
215+ [<Test>]
216+ let ``AsDateTime throws for invalid string`` () =
217+ ( fun () -> " not_a_date" .AsDateTime() |> ignore) |> should throw typeof< System.Exception>
218+
219+ [<Test>]
220+ let ``AsGuid throws for invalid string`` () =
221+ ( fun () -> " not_a_guid" .AsGuid() |> ignore) |> should throw typeof< System.Exception>
222+
223+ [<Test>]
224+ let ``AsInteger with custom culture`` () =
225+ let result = " 1234" .AsInteger( System.Globalization.CultureInfo.InvariantCulture)
226+ result |> should equal 1234
227+
228+ [<Test>]
229+ let ``AsDecimal with custom culture`` () =
230+ let result = " 123.45" .AsDecimal( System.Globalization.CultureInfo.InvariantCulture)
231+ result |> should equal 123.45 m
232+
233+ [<Test>]
234+ let ``AsFloat with custom culture`` () =
235+ let result = " 123.45" .AsFloat( System.Globalization.CultureInfo.InvariantCulture)
236+ result |> should equal 123.45
237+
238+ // Additional CSV parsing edge cases
239+ [<Test>]
240+ let ``readCsvFile handles empty fields correctly`` () =
241+ let sr = new StringReader( " a,,c\n ,b," )
242+ let actual = readCsvFile sr " ," '"' |> Seq.map fst |> Array.ofSeq
243+ let expected = [|[| " a" ; " " ; " c" |]; [| " " ; " b" ; " " |]|]
244+ actual |> should equal expected
245+
246+ [<Test>]
247+ let ``readCsvFile handles trailing comma`` () =
248+ let sr = new StringReader( " a,b,\n c,d," )
249+ let actual = readCsvFile sr " ," '"' |> Seq.map fst |> Array.ofSeq
250+ let expected = [|[| " a" ; " b" ; " " |]; [| " c" ; " d" ; " " |]|]
251+ actual |> should equal expected
252+
253+ [<Test>]
254+ let ``readCsvFile handles quoted empty string`` () =
255+ let sr = new StringReader( " a,\"\" ,c" )
256+ let actual = readCsvFile sr " ," '"' |> Seq.map fst |> Array.ofSeq
257+ let expected = [|[| " a" ; " " ; " c" |]|]
258+ actual |> should equal expected
259+
260+ [<Test>]
261+ let ``readCsvFile handles quotes at field boundaries`` () =
262+ let sr = new StringReader( " \" a\" ,\" b\" ,\" c\" " )
263+ let actual = readCsvFile sr " ," '"' |> Seq.map fst |> Array.ofSeq
264+ let expected = [|[| " a" ; " b" ; " c" |]|]
265+ actual |> should equal expected
266+
267+ [<Test>]
268+ let ``readCsvFile handles mixed quoted and unquoted fields`` () =
269+ let sr = new StringReader( " a,\" b,c\" ,d" )
270+ let actual = readCsvFile sr " ," '"' |> Seq.map fst |> Array.ofSeq
271+ let expected = [|[| " a" ; " b,c" ; " d" |]|]
272+ actual |> should equal expected
273+
274+ [<Test>]
275+ let ``readCsvFile handles Windows line endings`` () =
276+ let sr = new StringReader( " a,b\r\n c,d\r\n " )
277+ let actual = readCsvFile sr " ," '"' |> Seq.map fst |> Array.ofSeq
278+ let expected = [|[| " a" ; " b" |]; [| " c" ; " d" |]|]
279+ actual |> should equal expected
280+
281+ [<Test>]
282+ let ``readCsvFile handles Unix line endings`` () =
283+ let sr = new StringReader( " a,b\n c,d\n " )
284+ let actual = readCsvFile sr " ," '"' |> Seq.map fst |> Array.ofSeq
285+ let expected = [|[| " a" ; " b" |]; [| " c" ; " d" |]|]
286+ actual |> should equal expected
287+
288+ [<Test>]
289+ let ``readCsvFile handles Mac line endings`` () =
290+ let sr = new StringReader( " a,b\r c,d\r " )
291+ let actual = readCsvFile sr " ," '"' |> Seq.map fst |> Array.ofSeq
292+ let expected = [|[| " a" ; " b" |]; [| " c" ; " d" |]|]
293+ actual |> should equal expected
294+
295+ [<Test>]
296+ let ``readCsvFile handles tab separators`` () =
297+ let sr = new StringReader( " a\t b\t c\n d\t e\t f" )
298+ let actual = readCsvFile sr " \t " '"' |> Seq.map fst |> Array.ofSeq
299+ let expected = [|[| " a" ; " b" ; " c" |]; [| " d" ; " e" ; " f" |]|]
300+ actual |> should equal expected
301+
302+ [<Test>]
303+ let ``readCsvFile handles pipe separators`` () =
304+ let sr = new StringReader( " a|b|c\n d|e|f" )
305+ let actual = readCsvFile sr " |" '"' |> Seq.map fst |> Array.ofSeq
306+ let expected = [|[| " a" ; " b" ; " c" |]; [| " d" ; " e" ; " f" |]|]
307+ actual |> should equal expected
308+
309+ [<Test>]
310+ let ``readCsvFile handles single field`` () =
311+ let sr = new StringReader( " single" )
312+ let actual = readCsvFile sr " ," '"' |> Seq.map fst |> Array.ofSeq
313+ let expected = [|[| " single" |]|]
314+ actual |> should equal expected
315+
316+ [<Test>]
317+ let ``readCsvFile handles quoted field with internal quotes`` () =
318+ let sr = new StringReader( " \" She said \"\" Hello\"\" to me\" " )
319+ let actual = readCsvFile sr " ," '"' |> Seq.map fst |> Array.ofSeq
320+ let expected = [|[| " She said \" Hello\" to me" |]|]
321+ actual |> should equal expected
322+
323+ [<Test>]
324+ let ``readCsvFile handles BOM in stream`` () =
325+ // BOM (Byte Order Mark) should be handled gracefully
326+ let bomString = " \uFEFF a,b,c"
327+ let sr = new StringReader( bomString)
328+ let actual = readCsvFile sr " ," '"' |> Seq.map fst |> Array.ofSeq
329+ // Should handle BOM by treating it as part of first field
330+ actual.Length |> should be ( greaterThan 0 )
331+
332+ [<Test>]
333+ let ``CsvFile Parse handles various numeric formats`` () =
334+ let csv = CsvFile.Parse( " int,float,decimal\n 42,3.14,123.456" )
335+ let row = csv.Rows |> Seq.head
336+ row.[ " int" ] |> should equal " 42"
337+ row.[ " float" ] |> should equal " 3.14"
338+ row.[ " decimal" ] |> should equal " 123.456"
339+
340+ [<Test>]
341+ let ``CsvFile with different column counts when ignoreErrors is false`` () =
342+ ( fun () ->
343+ let csv = CsvFile.Parse( " a,b\n 1,2,3" , ignoreErrors= false )
344+ csv.Rows |> Seq.toArray |> ignore
345+ ) |> should throw typeof< System.Exception>
346+
347+ [<Test>]
348+ let ``CsvRow GetColumn by index works correctly`` () =
349+ let csv = CsvFile.Parse( " a,b,c\n 1,2,3" )
350+ let row = csv.Rows |> Seq.head
351+ row.GetColumn( 0 ) |> should equal " 1"
352+ row.GetColumn( 1 ) |> should equal " 2"
353+ row.GetColumn( 2 ) |> should equal " 3"
354+
355+ [<Test>]
356+ let ``CsvRow GetColumn by name works correctly`` () =
357+ let csv = CsvFile.Parse( " first,second,third\n x,y,z" )
358+ let row = csv.Rows |> Seq.head
359+ row.GetColumn( " first" ) |> should equal " x"
360+ row.GetColumn( " second" ) |> should equal " y"
361+ row.GetColumn( " third" ) |> should equal " z"
0 commit comments