-
-
Save BennieCopeland/dafeb5fcc36a7fd372b465588b91054f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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