Skip to content

Instantly share code, notes, and snippets.

@BennieCopeland
Forked from jwosty/DataGrid.fs
Created July 14, 2020 18:15
Show Gist options
  • Save BennieCopeland/dafeb5fcc36a7fd372b465588b91054f to your computer and use it in GitHub Desktop.
Save BennieCopeland/dafeb5fcc36a7fd372b465588b91054f to your computer and use it in GitHub Desktop.
open System
type private DataGridHelper =
static member inline IsValidRow (this: DataGrid<_,_,_>) row = row < this.rowInfo.Length && row >= 0
static member inline IsValidCol (this: DataGrid<_,_,_>) col = col < this.colInfo.Length && col >= 0
static member inline EnsureValidRow (this: DataGrid<_,_,_>) row = if not (DataGridHelper.IsValidRow this row) then raise (new IndexOutOfRangeException("row")) // TODO: F# vNext nameof(row)
static member inline EnsureValidCol (this: DataGrid<_,_,_>) col = if not (DataGridHelper.IsValidCol this col) then raise (new IndexOutOfRangeException("col")) // TODO: F# vNext nameof(col)
and DataGrid<'TRowInfo, 'TColInfo, 'TElement> =
private { rowInfo: 'TRowInfo[]; colInfo: 'TColInfo[]; elements: Map<int*int, 'TElement> }
member this.RowLength = this.rowInfo.Length
member this.ColLength = this.colInfo.Length
member this.Item (row,col) =
DataGridHelper.EnsureValidRow this row
DataGridHelper.EnsureValidCol this col
this.elements.[(row,col)]
member this.TryItem (row,col) = this.elements.TryFind (row,col)
member this.RowInfo = this.rowInfo :> 'TRowInfo seq
member this.ColInfo = this.colInfo :> 'TColInfo seq
member this.RowInfoAt rowIndex = this.rowInfo.[rowIndex]
member this.ColInfoAt colIndex = this.colInfo.[colIndex]
member this.TryRowInfoAt rowIndex = this.rowInfo |> Array.tryItem rowIndex
member this.TryColInfoAt colIndex = this.colInfo |> Array.tryItem colIndex
member this.Add ((row,col), item) = { this with elements = Map.add (row, col) item this.elements }
member this.AddOrUpdate ((row,col), updater) =
DataGridHelper.EnsureValidRow this row
DataGridHelper.EnsureValidCol this col
let x = Map.tryFind (row,col) this.elements
let x' = updater x
{ this with elements = Map.add (row,col) x' this.elements }
member this.UpdateRowInfo (row, updater) =
DataGridHelper.EnsureValidRow this row
let rowInfo' = Array.copy this.rowInfo
let rowInfoItem = rowInfo'.[row]
rowInfo'.[row] <- updater rowInfoItem
{ this with rowInfo = rowInfo' }
member this.UpdateColInfo (col, updater) =
DataGridHelper.EnsureValidCol this col
let colInfo' = Array.copy this.colInfo
let colInfoItem = colInfo'.[col]
colInfo'.[col] <- updater colInfoItem
{ this with colInfo = colInfo' }
module DataGrid =
let empty = { rowInfo = [||]; colInfo = [||]; elements = Map.empty }
let rowLength (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) = grid.RowLength
let colLength (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) = grid.ColLength
// This doesn't feel right I can't think of a better way
let private adjustElementsForNewRowOrCol i useRowElseUseCol xs =
let mapping =
if useRowElseUseCol then
(fun ((row,col),x) -> ((if row < i then row else row + 1),col),x)
else
(fun ((row,col),x) -> (row,(if col < i then col else col + 1)),x)
xs |> Map.toSeq
|> Seq.map mapping
|> Map.ofSeq
let insertRow rowIndex rowInfo (grid: DataGrid<'TRowInfo, 'TColInfo, 'TElement>) =
let prev, rest = Array.splitAt rowIndex grid.rowInfo
{ grid with
rowInfo = [| yield! prev; yield rowInfo; yield! rest |]
elements = adjustElementsForNewRowOrCol rowIndex true grid.elements
}
let insertCol colIndex rowInfo (grid: DataGrid<'TRowInfo, 'TColInfo, 'TElement>) =
let prev, rest = Array.splitAt colIndex grid.colInfo
{ grid with
colInfo = [| yield! prev; yield rowInfo; yield! rest |]
elements = adjustElementsForNewRowOrCol colIndex false grid.elements
}
let add (rowIndex, colIndex) (item: 'TElement) (grid: DataGrid<'TRowInfo, 'TColInfo, 'TElement>) = { grid with elements = Map.add (rowIndex,colIndex) item grid.elements }
let item (row, col) (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) = grid.[row,col]
let tryItem (row, col) (grid: DataGrid<'TRowInfo, 'TColInfo, 'TElement>) = grid.TryItem (row, col)
let rowInfo (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) = grid.RowInfo
let colInfo (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) = grid.ColInfo
let rowInfoAt rowIndex (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) = grid.RowInfoAt rowIndex
let colInfoAt colIndex (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) = grid.ColInfoAt colIndex
let tryRowInfoAt rowIndex (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) = grid.TryRowInfoAt rowIndex
let tryColInfoAt colIndex (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) = grid.TryColInfoAt colIndex
let private uncheckedRowAt rowIndex grid = seq { for colIndex in 0..grid.colInfo.Length - 1 -> grid.TryItem (rowIndex, colIndex) }
let private uncheckedColAt colIndex grid = seq { for rowIndex in 0..grid.rowInfo.Length - 1 -> grid.TryItem (rowIndex, colIndex) }
let rowAt rowIndex (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) =
DataGridHelper.EnsureValidRow grid rowIndex
uncheckedRowAt rowIndex grid
let tryRowAt rowIndex (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) =
if DataGridHelper.IsValidRow grid rowIndex then Some (uncheckedRowAt rowIndex grid)
else None
let colAt colIndex (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) =
DataGridHelper.EnsureValidCol grid colIndex
uncheckedColAt colIndex grid
let tryColAt colIndex (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) =
if DataGridHelper.IsValidCol grid colIndex then Some (uncheckedColAt colIndex grid)
else None
let setRowInfo rowIndex rowInfo (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) =
{ grid with rowInfo = grid.rowInfo |> Array.mapi (fun i x -> if i = rowIndex then rowInfo else x) }
let setColInfo colIndex colInfo (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) =
{ grid with colInfo = grid.colInfo |> Array.mapi (fun i x -> if i = colIndex then colInfo else x) }
let remove (row,col) (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) =
{ grid with elements = grid.elements |> Map.remove (row,col) }
let removeRow rowIndex (grid: DataGrid<'TRowInfo, 'TColInfo, 'TElement>) =
let elements' = seq { for ((row,col),x) in Map.toSeq grid.elements do
if row < rowIndex then
yield (row,col),x
else if row > rowIndex then
yield (row - 1,col),x }
|> Map.ofSeq
{ grid with
rowInfo = [|
for i in 0 .. grid.rowInfo.Length - 1 do
if i <> rowIndex then yield grid.rowInfo.[i]
|]
elements = elements'
}
let removeCol colIndex (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) =
let elements' = seq { for ((row,col),x) in Map.toSeq grid.elements do
if col < colIndex then
yield (row,col),x
else if col > colIndex then
yield (row,col - 1),x}
|> Map.ofSeq
{ grid with
colInfo = [|
for i in 0 .. grid.colInfo.Length - 1 do
if i <> colIndex then yield grid.colInfo.[i]
|]
elements = elements'
}
let map (mapping: int*int -> 'a -> 'b) (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) =
{ elements = grid.elements |> Map.map (fun k v -> mapping k v); rowInfo = grid.rowInfo; colInfo = grid.colInfo }
let mapRowInfo (mapping: int -> 'a -> 'b) (grid: DataGrid<'a, 'TColInfo, 'TElement>) =
{ rowInfo = grid.rowInfo |> Array.mapi mapping; colInfo = grid.colInfo; elements = grid.elements }
let mapColInfo (mapping: int -> 'a -> 'b) (grid: DataGrid<'TRowInfo, 'a, 'TElement>) =
{ rowInfo = grid.rowInfo; colInfo = Array.mapi mapping grid.colInfo; elements = grid.elements }
let update (row,col) (updater: 'TElement option -> 'TElement) (grid: DataGrid<'TRowInfo, 'TColInfo, 'TElement>) = grid.AddOrUpdate ((row,col), updater)
let updateRowInfo row updater (grid: DataGrid<'TRowInfo, 'TColInfo, 'TElement>) = grid.UpdateRowInfo (row, updater)
let updateColInfo row updater (grid: DataGrid<'TRowInfo, 'TColInfo, 'TElement>) = grid.UpdateColInfo (row, updater)
let toSeq (grid: DataGrid<'TRowInfo, 'TColInfo, 'a>) = grid.elements |> Map.toSeq
open System
#if FABLE_COMPILER
open AsyncHack
open Fable.Mocha
open Fable.Mocha.Flip
#else
open Expecto
open Expecto.Flip
#endif
open StageForge.Core
type RowInfo = { rowName: string }
type ColInfo = { colName: string }
module RowInfo =
let create rowName = { rowName = rowName }
module ColInfo =
let create colName = { colName = colName }
// FIXME: anonymous records https://github.com/mono/mono/issues/16763
type AgeRange = { ageRange: string }
type FavFruit = { favoriteFruit: string }
let make2Row3ColTestGrid () =
DataGrid.empty
|> DataGrid.insertRow 0 "first row" |> DataGrid.insertRow 1 "second row"
|> DataGrid.insertCol 0 "first col" |> DataGrid.insertCol 1 "second col" |> DataGrid.insertCol 2 "third col"
|> DataGrid.add (0,0) "element 0,0"
let tests =
testList "DataGrid" [
testCase "attempting to retrieve an item from an empty grid should throw an exception" (fun () ->
let grid = DataGrid.empty
(fun () -> grid.[0,0] |> ignore) |> Expect.throws ".Item"
)
testCase "insertRow and insertCol should create rows/columns on empty grids" (fun () ->
let grid1 = DataGrid.empty |> DataGrid.insertRow 0 { rowName = "row 0" }
let grid2 = DataGrid.empty |> DataGrid.insertCol 0 { colName = "col a" }
grid1 |> DataGrid.rowInfo |> Expect.sequenceEqual "grid1 rows" [{ rowName = "row 0" }]
grid1 |> DataGrid.colInfo |> Expect.sequenceEqual "grid1 cols" Seq.empty
grid2 |> DataGrid.rowInfo |> Expect.sequenceEqual "grid2 rows" Seq.empty
grid2 |> DataGrid.colInfo |> Expect.sequenceEqual "grid2 cols" [{ colName = "col a" }]
)
testCase "using insertRow and insertCol in the middle should perform a proper insert" (fun () ->
let grid1 = DataGrid.empty |> DataGrid.insertRow 0 { rowName = "row 0" } |> DataGrid.insertRow 1 { rowName = "row 3" }
|> DataGrid.insertRow 1 { rowName = "row 2" } |> DataGrid.insertRow 1 { rowName = "row 1" }
let grid2 = DataGrid.empty |> DataGrid.insertCol 0 { colName = "col a" } |> DataGrid.insertCol 1 { colName = "col d" }
|> DataGrid.insertCol 1 { colName = "col c" } |> DataGrid.insertCol 1 { colName = "col b" }
grid1 |> DataGrid.rowInfo |> Expect.sequenceEqual "grid1 rows" ([for r in 0..3 -> { rowName = sprintf "row %d" r }])
grid2 |> DataGrid.colInfo |> Expect.sequenceEqual "grid2 cols" ([for c in 'a'..'d' -> { colName = sprintf "col %c" c }])
)
testCase "inserting a 2nd row or col at the last index should append it" (fun () ->
let grid1 = DataGrid.empty |> DataGrid.insertRow 0 { rowName = "row 0" } |> DataGrid.insertRow 1 { rowName = "row 1" }
let grid2 = DataGrid.empty |> DataGrid.insertCol 0 { colName = "col a" } |> DataGrid.insertCol 1 { colName = "col b" }
grid1 |> DataGrid.rowInfo |> Expect.sequenceEqual "grid1 rows" [{ rowName = "row 0" }; { rowName = "row 1" }]
grid2 |> DataGrid.colInfo |> Expect.sequenceEqual "grid2 cols" [{ colName = "col a" }; { colName = "col b" }]
)
testCase "insertRow should ajust grid elements" (fun () ->
let grid =
DataGrid.empty
|> DataGrid.insertRow 0 { rowName = "row a" } |> DataGrid.insertRow 1 { rowName = "row b" }
|> DataGrid.insertCol 0 { colName = "col a" }
|> DataGrid.add (0,0) "banana"
|> DataGrid.add (1,0) "apple"
let grid' = grid |> DataGrid.insertRow 1 { rowName = "row between a and b" }
grid' |> DataGrid.tryItem (0,0) |> Expect.equal "banana" (Some "banana")
grid' |> DataGrid.tryItem (1,0) |> Expect.equal "newly empty item" None
grid' |> DataGrid.tryItem (2,0) |> Expect.equal "apple" (Some "apple")
)
testCase "insertCol should ajust grid elements" (fun () ->
let grid =
DataGrid.empty
|> DataGrid.insertRow 0 { rowName = "row a" }
|> DataGrid.insertCol 0 { colName = "col a" } |> DataGrid.insertCol 1 { colName = "col b"}
|> DataGrid.add (0,0) "banana"
|> DataGrid.add (0,1) "apple"
let grid' = grid |> DataGrid.insertCol 1 { colName = "col between a and b" }
grid' |> DataGrid.tryItem (0,0) |> Expect.equal "banana" (Some "banana")
grid' |> DataGrid.tryItem (0,1) |> Expect.equal "newly empty item" None
grid' |> DataGrid.tryItem (0,2) |> Expect.equal "apple" (Some "apple")
)
testCase "adding items at an in-bounds locations should create retrievable elements" (fun () ->
let grid = DataGrid.empty
|> DataGrid.insertRow 0 { rowName = "row 0" } |> DataGrid.insertRow 1 { rowName = "row 1" }
|> DataGrid.insertCol 0 { colName = "col a" }
|> DataGrid.add (0, 0) "0a" |> DataGrid.add (1, 0) "1a"
grid |> DataGrid.item (0,0) |> Expect.equal "row 0 col a" "0a"
grid.[1,0] |> Expect.equal "row 1 col a" "1a"
)
testCase "adding items at locations that already have items should replace the old item" (fun () ->
make2Row3ColTestGrid ()
|> DataGrid.add (1,1) "element (1,1)"
|> DataGrid.add (1,1) "banana element (1,1)"
|> DataGrid.item (1,1) |> Expect.equal "add to existing location" "banana element (1,1)"
)
testCase "setRowInfo and setColInfo should return a new DataGrid with the given row or col info replaced" (fun () ->
let grid = DataGrid.empty
|> DataGrid.insertRow 0 { rowName = "row foo" } |> DataGrid.insertRow 1 { rowName = "row bar" }
|> DataGrid.insertCol 0 { colName = "col foo" } |> DataGrid.insertCol 1 { colName = "col bar" }
let grid' = grid |> DataGrid.setRowInfo 0 { rowName = "ROW FOO" } |> DataGrid.setColInfo 1 { colName = "COL BAR" }
grid' |> DataGrid.rowInfo |> Expect.sequenceEqual "change row 0" [{ rowName = "ROW FOO" }; { rowName = "row bar" }]
grid' |> DataGrid.colInfo |> Expect.sequenceEqual "change col 1" [{ colName = "col foo" }; { colName = "COL BAR" }]
)
testCase "item should throw IndexOutOfRangeException for negative row or column indices" (fun () ->
let grid = DataGrid.empty
|> DataGrid.insertRow 0 { rowName = "row 0" }
|> DataGrid.insertCol 0 { colName = "col a" } |> DataGrid.insertCol 1 { colName = "col b" }
(fun () -> grid.Item (-1,0) |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "negative row index"
(fun () -> grid.Item (0,-1) |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "negative col index"
(fun () -> grid.Item (-1,-1) |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "negative row and col index"
)
testCase "item should throw IndexOutOfRangeException for row or column indices that are too large" (fun () ->
let grid = DataGrid.empty
|> DataGrid.insertRow 0 { rowName = "row 0" } |> DataGrid.insertRow 1 { rowName = "row 1" }
|> DataGrid.insertCol 0 { colName = "col a" } |> DataGrid.insertCol 1 { colName = "col 1" } |> DataGrid.insertCol 2 { colName = "col 2" }
|> DataGrid.add (0, 0) "0a"
(fun () -> grid.Item (2,0) |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "too large row index"
(fun () -> grid.Item (0,3) |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "too large col index"
)
testCase "tryItem should return the element at the row/col index, or None when rol/col indices are out of range" (fun () ->
let grid = DataGrid.empty
|> DataGrid.insertRow 0 { rowName = "row 0" } |> DataGrid.insertRow 1 { rowName = "row 1" }
|> DataGrid.insertCol 0 { colName = "col a" } |> DataGrid.insertCol 1 { colName = "col 1" } |> DataGrid.insertCol 2 { colName = "col 2" }
|> DataGrid.add (0, 0) "0a"
grid |> DataGrid.tryItem (0,0) |> Expect.equal "0,0" (Some "0a")
grid |> DataGrid.tryItem (1,0) |> Expect.equal "1,0" None
grid |> DataGrid.tryItem (100,0) |> Expect.equal "100,0" None
)
testCase "tryRowInfo and tryColInfo should return None when the row/col is out of bounds" (fun () ->
let grid = DataGrid.empty
|> DataGrid.insertRow 0 { rowName = "row 0" } |> DataGrid.insertRow 1 { rowName = "row 1" }
|> DataGrid.insertCol 0 { colName = "col a" }
grid |> DataGrid.tryRowInfoAt -1 |> Expect.equal "nonexistent row -1" None
grid |> DataGrid.tryColInfoAt -1 |> Expect.equal "nonexistent col -1" None
grid |> DataGrid.tryRowInfoAt 2 |> Expect.equal "nonexistent row 2" None
grid |> DataGrid.tryColInfoAt 1 |> Expect.equal "nonexistent col 1" None
)
testCase "individual rows and columns should be retrievable" (fun () ->
let grid = DataGrid.empty
|> DataGrid.insertRow 0 { rowName = "row 0" } |> DataGrid.insertRow 1 { rowName = "row 1" }
|> DataGrid.insertCol 0 { colName = "col a" } |> DataGrid.insertCol 1 { colName = "col b" }
|> DataGrid.add (0,0) 'x' |> DataGrid.add (0,1) 'y'
|> DataGrid.add (1,0) 'z'
grid |> DataGrid.rowAt 0 |> Expect.sequenceEqual "row 0" [Some 'x'; Some 'y']
grid |> DataGrid.rowAt 1 |> Expect.sequenceEqual "row 1" [Some 'z'; None]
grid |> DataGrid.colAt 0 |> Expect.sequenceEqual "col a" [Some 'x'; Some 'z']
grid |> DataGrid.colAt 1 |> Expect.sequenceEqual "col b" [Some 'y'; None]
)
testCase "rowAt and colAt should throw exception for invalid row/col index" (fun () ->
let grid = DataGrid.empty |> DataGrid.insertRow 0 { rowName = "row 0" } |> DataGrid.insertCol 0 { colName = "col a" } |> DataGrid.insertCol 1 { colName = "col b" }
(fun () -> grid |> DataGrid.rowAt -1 |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "negative row index"
(fun () -> grid |> DataGrid.rowAt 2 |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "out-of-bounds row index"
(fun () -> grid |> DataGrid.colAt -1 |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "negative col index"
(fun () -> grid |> DataGrid.colAt 3 |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "out-of-bounds col index"
)
testCase "tryRowAt and tryColAt should return None for invalid row/col index" (fun () ->
let grid = DataGrid.empty |> DataGrid.insertRow 0 { rowName = "row 0" } |> DataGrid.insertCol 0 { colName = "col a" } |> DataGrid.insertCol 1 { colName = "col b" }
grid |> DataGrid.tryRowAt -1 |> Expect.equal "negative row index" None
grid |> DataGrid.tryRowAt 2 |> Expect.equal "out-of-bounds row index" None
grid |> DataGrid.tryColAt -1 |> Expect.equal "negative col index" None
)
testCase "removeRow and removeCol should remove rowInfo and elements" (fun () ->
let rows = [[(0,0),0; (0,1),1; (0,2),2]
[(1,0),10; (1,1),11; (1,2),12]
[(2,0),20; (2,1),21; (2,2),22]]
let grid = DataGrid.empty
|> DataGrid.insertRow 0 { ageRange = "0-19" } |> DataGrid.insertRow 1 { ageRange = "20-29" } |> DataGrid.insertRow 2 { ageRange = "30-39" }
|> DataGrid.insertCol 0 { favoriteFruit = "apple" } |> DataGrid.insertCol 1 { favoriteFruit = "orange" } |> DataGrid.insertCol 2 { favoriteFruit = "banana" }
let grid = rows |> List.concat |> List.fold (fun acc (pos,elem) -> DataGrid.add pos elem acc) grid
let noTwentySomethings = grid |> DataGrid.removeRow 1
noTwentySomethings |> DataGrid.rowInfo |> Expect.sequenceEqual "remove twenty somethings" [{ ageRange = "0-19" }; { ageRange = "30-39" }]
// deleting a row affects the indices; rows to the right get shifted down an index
let row i = [for (_,_),x in rows.[i] -> Some x]
noTwentySomethings |> DataGrid.rowAt 0 |> Expect.sequenceEqual "first row" (row 0)
noTwentySomethings |> DataGrid.rowAt 1 |> Expect.sequenceEqual "second row" (row 2) // remember, shifted down an index!
noTwentySomethings |> DataGrid.tryRowAt 2 |> Expect.equal "third row" None
let noOrange = grid |> DataGrid.removeCol 1
noOrange |> DataGrid.colInfo |> Expect.sequenceEqual "remove orange col" [{ favoriteFruit = "apple" }; { favoriteFruit = "banana" }]
// deleting a col also affects the indices; cols after the removed one get shifted down an index
noOrange |> DataGrid.colAt 0 |> Expect.sequenceEqual "first col" [Some 0; Some 10; Some 20]
noOrange |> DataGrid.colAt 1 |> Expect.sequenceEqual "second col" [Some 2; Some 12; Some 22]
noOrange |> DataGrid.tryColAt 2 |> Expect.equal "third col" None
)
testCase "remove should return a new grid with the element at the given row,col index removed" (fun () ->
let grid =
DataGrid.empty
|> DataGrid.insertRow 0 { rowName = "row 0" } |> DataGrid.insertRow 0 { rowName = "row 1" }
|> DataGrid.insertCol 0 { colName = "col a" }
|> DataGrid.add (0,0) "0a" |> DataGrid.add (1,0) "1a"
let grid' = grid |> DataGrid.remove (0,0)
grid' |> DataGrid.tryItem (0,0) |> Expect.equal "0,0" None
grid' |> DataGrid.tryItem (1,0) |> Expect.equal "0,0" (Some "1a")
)
testCase "map should return a new DataGrid with all the elements transformed using the mapping function" (fun () ->
let grid = DataGrid.empty
|> DataGrid.insertRow 0 () |> DataGrid.insertRow 1 () |> DataGrid.insertCol 0 () |> DataGrid.insertCol 1 ()
|> DataGrid.add (0,0) 10 |> DataGrid.add (0,1) 20 |> DataGrid.add (1,0) 30 |> DataGrid.add (1,1) 40
let grid' = grid |> DataGrid.map (fun (row,col) x -> row + col + x + 1)
grid'.[0,0] |> Expect.equal "0,0" 11
grid'.[0,1] |> Expect.equal "0,1" 22
grid'.[1,0] |> Expect.equal "1,0" 32
grid'.[1,1] |> Expect.equal "1,1" 43
)
testCase "toSeq should return a sequence that enumerates all the existing elements along with their indices" (fun () ->
let grid = DataGrid.empty
|> DataGrid.insertRow 0 { ageRange = "0-19" } |> DataGrid.insertRow 1 { ageRange = "20-29" } |> DataGrid.insertRow 2 { ageRange = "30-39" }
|> DataGrid.insertCol 0 { favoriteFruit = "apple" } |> DataGrid.insertCol 1 { favoriteFruit = "orange" } |> DataGrid.insertCol 2 { favoriteFruit = "banana" }
let elements = [(0,0),10; (0,1),11; (0,2),12
(1,0),5; (1,1),9; (1,2),6
(2,0),15; (2,1),20 (* last elt purposefully omitted for testing purposes *) ]
let grid' = List.fold (fun grid ((row,col),x) -> DataGrid.add (row,col) x grid) grid elements
grid' |> DataGrid.toSeq
|> Expect.sequenceEqual "DataGrid.toSeq" elements
)
testCase "mapRowInfo and mapColInfo should return a new DataGrid with the rowInfo and colInfo transformed accordingly" (fun () ->
let grid = DataGrid.empty
|> DataGrid.insertRow 0 "one fish" |> DataGrid.insertRow 1 "two fish"
|> DataGrid.insertCol 0 "red fish" |> DataGrid.insertCol 1 "blue fish"
let grid' = grid |> DataGrid.mapRowInfo (fun rowI rowName -> { rowName = sprintf "%s (%d)" rowName rowI })
|> DataGrid.mapColInfo (fun colI colName -> { colName = sprintf "%s (%d)" colName colI })
grid' |> DataGrid.rowInfo |> Expect.sequenceEqual "mapped rowInfo" [{ rowName = "one fish (0)" }; { rowName = "two fish (1)" }]
grid' |> DataGrid.colInfo |> Expect.sequenceEqual "mapped colInfo" [{ colName = "red fish (0)" }; { colName = "blue fish (1)" }]
)
testCase "rowLength and colLength should return the number of rows and columns, respectively" (fun () ->
let grid = DataGrid.empty
|> DataGrid.insertRow 0 "first row" |> DataGrid.insertRow 1 "second row"
|> DataGrid.insertCol 0 "first col" |> DataGrid.insertCol 1 "second col" |> DataGrid.insertCol 2 "third col"
grid |> DataGrid.rowLength |> Expect.equal "rowLength" 2
grid |> DataGrid.colLength |> Expect.equal "colLength" 3
)
testCase "equality should work" (fun () ->
let grid1, grid1Copy = make2Row3ColTestGrid (), make2Row3ColTestGrid ()
grid1Copy |> Expect.equal "should be idential DataGrids" grid1
grid1 |> DataGrid.add (1,1) "element 1,1" |> Expect.notEqual "should be extra element" grid1
grid1 |> DataGrid.insertRow 0 "extra row" |> Expect.notEqual "should be inequal - extra row" grid1
grid1 |> DataGrid.insertCol 0 "extra col" |> Expect.notEqual "should be inequal - extra col" grid1
)
testCase "addOrUpdate should fail when called using an invalid index" (fun () ->
(fun () -> DataGrid.empty |> DataGrid.update (0,0) (fun _ -> "i should not exist") |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "update item in empty grid should throw"
(fun () -> make2Row3ColTestGrid () |> DataGrid.update (3,0) (fun _ -> "i should not exist") |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "update out of bounds item should throw"
)
testCase "update, given coords that have no item, should add a new item created from evaluating updater with None" (fun () ->
make2Row3ColTestGrid ()
|> DataGrid.update (1,1) (function None -> "element (1,1)" | Some _ -> failwith "update() should not be passing Some() in this scenario")
|> DataGrid.item (1,1) |> Expect.equal "item should be added" "element (1,1)"
)
testCase "update, given coords that already have an item, should add a new item created from evaluating updater with the old element" (fun () ->
make2Row3ColTestGrid ()
|> DataGrid.add (1,1) "element (1,1)"
|> DataGrid.update (1,1) (function Some x -> "banana " + x | None -> failwith "update() should not be passing None in the scenario")
|> DataGrid.item (1,1) |> Expect.equal "item should be replaced" "banana element (1,1)"
)
testCase "updateRowInfo and updateColInfo should throw when given invalid indices" (fun () ->
(fun () -> DataGrid.empty |> DataGrid.updateRowInfo 0 (fun _ -> "i should not exist") |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "updateRowInfo in empty grid should throw"
(fun () -> make2Row3ColTestGrid () |> DataGrid.updateRowInfo 2 (fun _ -> "i should not exist") |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "updateRowInfo with out of bounds row should throw"
(fun () -> DataGrid.empty |> DataGrid.updateColInfo 0 (fun _ -> "i should not exist") |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "updateColInfo in empty grid should throw"
(fun () -> make2Row3ColTestGrid () |> DataGrid.updateColInfo 3 (fun _ -> "i should not exist") |> ignore) |> Expect.throwsT<IndexOutOfRangeException> "udpateColInfo with out of bounds col should throw"
)
testCase "updateRow and updateCol should update the row info or col info (respectively) with new data from the given updater function" (fun () ->
make2Row3ColTestGrid ()
|> DataGrid.updateRowInfo 1 (fun x -> "banana " + x)
|> DataGrid.rowInfoAt 1 |> Expect.equal "row info index 1" "banana second row"
make2Row3ColTestGrid ()
|> DataGrid.updateColInfo 2 (fun x -> "cherry " + x)
|> DataGrid.colInfoAt 2 |> Expect.equal "col info index 2" "cherry third col"
)
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment