using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Terminal.Gui;
using Xunit;
using System.Globalization;
using Xunit.Abstractions;
using System.Reflection;
namespace Terminal.Gui.ViewTests {
public class TableViewTests {
readonly ITestOutputHelper output;
public TableViewTests (ITestOutputHelper output)
{
this.output = output;
}
[Fact]
public void EnsureValidScrollOffsets_WithNoCells ()
{
var tableView = new TableView ();
Assert.Equal (0, tableView.RowOffset);
Assert.Equal (0, tableView.ColumnOffset);
// Set empty table
tableView.Table = new DataTable ();
// Since table has no rows or columns scroll offset should default to 0
tableView.EnsureValidScrollOffsets ();
Assert.Equal (0, tableView.RowOffset);
Assert.Equal (0, tableView.ColumnOffset);
}
[Fact]
public void EnsureValidScrollOffsets_LoadSmallerTable ()
{
var tableView = new TableView ();
tableView.Bounds = new Rect (0, 0, 25, 10);
Assert.Equal (0, tableView.RowOffset);
Assert.Equal (0, tableView.ColumnOffset);
// Set big table
tableView.Table = BuildTable (25, 50);
// Scroll down and along
tableView.RowOffset = 20;
tableView.ColumnOffset = 10;
tableView.EnsureValidScrollOffsets ();
// The scroll should be valid at the moment
Assert.Equal (20, tableView.RowOffset);
Assert.Equal (10, tableView.ColumnOffset);
// Set small table
tableView.Table = BuildTable (2, 2);
// Setting a small table should automatically trigger fixing the scroll offsets to ensure valid cells
Assert.Equal (0, tableView.RowOffset);
Assert.Equal (0, tableView.ColumnOffset);
// Trying to set invalid indexes should not be possible
tableView.RowOffset = 20;
tableView.ColumnOffset = 10;
Assert.Equal (1, tableView.RowOffset);
Assert.Equal (1, tableView.ColumnOffset);
}
[Fact]
[AutoInitShutdown]
public void Redraw_EmptyTable ()
{
var tableView = new TableView ();
tableView.ColorScheme = new ColorScheme();
tableView.Bounds = new Rect (0, 0, 25, 10);
// Set a table with 1 column
tableView.Table = BuildTable (1, 50);
tableView.Redraw(tableView.Bounds);
tableView.Table.Columns.Remove(tableView.Table.Columns[0]);
tableView.Redraw(tableView.Bounds);
}
[Fact]
public void SelectedCellChanged_NotFiredForSameValue ()
{
var tableView = new TableView () {
Table = BuildTable (25, 50)
};
bool called = false;
tableView.SelectedCellChanged += (e) => { called = true; };
Assert.Equal (0, tableView.SelectedColumn);
Assert.False (called);
// Changing value to same as it already was should not raise an event
tableView.SelectedColumn = 0;
Assert.False (called);
tableView.SelectedColumn = 10;
Assert.True (called);
}
[Fact]
public void SelectedCellChanged_SelectedColumnIndexesCorrect ()
{
var tableView = new TableView () {
Table = BuildTable (25, 50)
};
bool called = false;
tableView.SelectedCellChanged += (e) => {
called = true;
Assert.Equal (0, e.OldCol);
Assert.Equal (10, e.NewCol);
};
tableView.SelectedColumn = 10;
Assert.True (called);
}
[Fact]
public void SelectedCellChanged_SelectedRowIndexesCorrect ()
{
var tableView = new TableView () {
Table = BuildTable (25, 50)
};
bool called = false;
tableView.SelectedCellChanged += (e) => {
called = true;
Assert.Equal (0, e.OldRow);
Assert.Equal (10, e.NewRow);
};
tableView.SelectedRow = 10;
Assert.True (called);
}
[Fact]
public void Test_SumColumnWidth_UnicodeLength ()
{
Assert.Equal (11, "hello there".Sum (c => Rune.ColumnWidth (c)));
// Creates a string with the peculiar (french?) r symbol
String surrogate = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables";
// The unicode width of this string is shorter than the string length!
Assert.Equal (14, surrogate.Sum (c => Rune.ColumnWidth (c)));
Assert.Equal (15, surrogate.Length);
}
[Fact]
public void IsSelected_MultiSelectionOn_Vertical ()
{
var tableView = new TableView () {
Table = BuildTable (25, 50),
MultiSelect = true
};
// 3 cell vertical selection
tableView.SetSelection (1, 1, false);
tableView.SetSelection (1, 3, true);
Assert.False (tableView.IsSelected (0, 0));
Assert.False (tableView.IsSelected (1, 0));
Assert.False (tableView.IsSelected (2, 0));
Assert.False (tableView.IsSelected (0, 1));
Assert.True (tableView.IsSelected (1, 1));
Assert.False (tableView.IsSelected (2, 1));
Assert.False (tableView.IsSelected (0, 2));
Assert.True (tableView.IsSelected (1, 2));
Assert.False (tableView.IsSelected (2, 2));
Assert.False (tableView.IsSelected (0, 3));
Assert.True (tableView.IsSelected (1, 3));
Assert.False (tableView.IsSelected (2, 3));
Assert.False (tableView.IsSelected (0, 4));
Assert.False (tableView.IsSelected (1, 4));
Assert.False (tableView.IsSelected (2, 4));
}
[Fact]
public void IsSelected_MultiSelectionOn_Horizontal ()
{
var tableView = new TableView () {
Table = BuildTable (25, 50),
MultiSelect = true
};
// 2 cell horizontal selection
tableView.SetSelection (1, 0, false);
tableView.SetSelection (2, 0, true);
Assert.False (tableView.IsSelected (0, 0));
Assert.True (tableView.IsSelected (1, 0));
Assert.True (tableView.IsSelected (2, 0));
Assert.False (tableView.IsSelected (3, 0));
Assert.False (tableView.IsSelected (0, 1));
Assert.False (tableView.IsSelected (1, 1));
Assert.False (tableView.IsSelected (2, 1));
Assert.False (tableView.IsSelected (3, 1));
}
[Fact]
public void IsSelected_MultiSelectionOn_BoxSelection ()
{
var tableView = new TableView () {
Table = BuildTable (25, 50),
MultiSelect = true
};
// 4 cell horizontal in box 2x2
tableView.SetSelection (0, 0, false);
tableView.SetSelection (1, 1, true);
Assert.True (tableView.IsSelected (0, 0));
Assert.True (tableView.IsSelected (1, 0));
Assert.False (tableView.IsSelected (2, 0));
Assert.True (tableView.IsSelected (0, 1));
Assert.True (tableView.IsSelected (1, 1));
Assert.False (tableView.IsSelected (2, 1));
Assert.False (tableView.IsSelected (0, 2));
Assert.False (tableView.IsSelected (1, 2));
Assert.False (tableView.IsSelected (2, 2));
}
[AutoInitShutdown]
[Fact]
public void PageDown_ExcludesHeaders ()
{
var tableView = new TableView () {
Table = BuildTable (25, 50),
MultiSelect = true,
Bounds = new Rect (0, 0, 10, 5)
};
// Header should take up 2 lines
tableView.Style.ShowHorizontalHeaderOverline = false;
tableView.Style.ShowHorizontalHeaderUnderline = true;
tableView.Style.AlwaysShowHeaders = false;
// ensure that TableView has the input focus
Application.Top.Add (tableView);
Application.Top.FocusFirst ();
Assert.True (tableView.HasFocus);
Assert.Equal (0, tableView.RowOffset);
tableView.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ()));
// window height is 5 rows 2 are header so page down should give 3 new rows
Assert.Equal (3, tableView.SelectedRow);
Assert.Equal (1, tableView.RowOffset);
// header is no longer visible so page down should give 5 new rows
tableView.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ()));
Assert.Equal (8, tableView.SelectedRow);
Assert.Equal (4, tableView.RowOffset);
}
[Fact]
public void DeleteRow_SelectAll_AdjustsSelectionToPreventOverrun ()
{
// create a 4 by 4 table
var tableView = new TableView () {
Table = BuildTable (4, 4),
MultiSelect = true,
Bounds = new Rect (0, 0, 10, 5)
};
tableView.SelectAll ();
Assert.Equal (16, tableView.GetAllSelectedCells ().Count ());
// delete one of the columns
tableView.Table.Columns.RemoveAt (2);
// table should now be 3x4
Assert.Equal (12, tableView.GetAllSelectedCells ().Count ());
// remove a row
tableView.Table.Rows.RemoveAt (1);
// table should now be 3x3
Assert.Equal (9, tableView.GetAllSelectedCells ().Count ());
}
[Fact]
public void DeleteRow_SelectLastRow_AdjustsSelectionToPreventOverrun ()
{
// create a 4 by 4 table
var tableView = new TableView () {
Table = BuildTable (4, 4),
MultiSelect = true,
Bounds = new Rect (0, 0, 10, 5)
};
// select the last row
tableView.MultiSelectedRegions.Clear ();
tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (0, 3), new Rect (0, 3, 4, 1)));
Assert.Equal (4, tableView.GetAllSelectedCells ().Count ());
// remove a row
tableView.Table.Rows.RemoveAt (0);
tableView.EnsureValidSelection ();
// since the selection no longer exists it should be removed
Assert.Empty (tableView.MultiSelectedRegions);
}
[Theory]
[InlineData (true)]
[InlineData (false)]
public void GetAllSelectedCells_SingleCellSelected_ReturnsOne (bool multiSelect)
{
var tableView = new TableView () {
Table = BuildTable (3, 3),
MultiSelect = multiSelect,
Bounds = new Rect (0, 0, 10, 5)
};
tableView.SetSelection (1, 1, false);
Assert.Single (tableView.GetAllSelectedCells ());
Assert.Equal (new Point (1, 1), tableView.GetAllSelectedCells ().Single ());
}
[Fact]
public void GetAllSelectedCells_SquareSelection_ReturnsFour ()
{
var tableView = new TableView () {
Table = BuildTable (3, 3),
MultiSelect = true,
Bounds = new Rect (0, 0, 10, 5)
};
// move cursor to 1,1
tableView.SetSelection (1, 1, false);
// spread selection across to 2,2 (e.g. shift+right then shift+down)
tableView.SetSelection (2, 2, true);
var selected = tableView.GetAllSelectedCells ().ToArray ();
Assert.Equal (4, selected.Length);
Assert.Equal (new Point (1, 1), selected [0]);
Assert.Equal (new Point (2, 1), selected [1]);
Assert.Equal (new Point (1, 2), selected [2]);
Assert.Equal (new Point (2, 2), selected [3]);
}
[Fact]
public void GetAllSelectedCells_SquareSelection_FullRowSelect ()
{
var tableView = new TableView () {
Table = BuildTable (3, 3),
MultiSelect = true,
FullRowSelect = true,
Bounds = new Rect (0, 0, 10, 5)
};
// move cursor to 1,1
tableView.SetSelection (1, 1, false);
// spread selection across to 2,2 (e.g. shift+right then shift+down)
tableView.SetSelection (2, 2, true);
var selected = tableView.GetAllSelectedCells ().ToArray ();
Assert.Equal (6, selected.Length);
Assert.Equal (new Point (0, 1), selected [0]);
Assert.Equal (new Point (1, 1), selected [1]);
Assert.Equal (new Point (2, 1), selected [2]);
Assert.Equal (new Point (0, 2), selected [3]);
Assert.Equal (new Point (1, 2), selected [4]);
Assert.Equal (new Point (2, 2), selected [5]);
}
[Fact]
public void GetAllSelectedCells_TwoIsolatedSelections_ReturnsSix ()
{
var tableView = new TableView () {
Table = BuildTable (20, 20),
MultiSelect = true,
Bounds = new Rect (0, 0, 10, 5)
};
/*
Sets up disconnected selections like:
00000000000
01100000000
01100000000
00000001100
00000000000
*/
tableView.MultiSelectedRegions.Clear ();
tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (1, 1), new Rect (1, 1, 2, 2)));
tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (7, 3), new Rect (7, 3, 2, 1)));
tableView.SelectedColumn = 8;
tableView.SelectedRow = 3;
var selected = tableView.GetAllSelectedCells ().ToArray ();
Assert.Equal (6, selected.Length);
Assert.Equal (new Point (1, 1), selected [0]);
Assert.Equal (new Point (2, 1), selected [1]);
Assert.Equal (new Point (1, 2), selected [2]);
Assert.Equal (new Point (2, 2), selected [3]);
Assert.Equal (new Point (7, 3), selected [4]);
Assert.Equal (new Point (8, 3), selected [5]);
}
[Fact]
public void TableView_ExpandLastColumn_True ()
{
var tv = SetUpMiniTable ();
// the thing we are testing
tv.Style.ExpandLastColumn = true;
tv.Redraw (tv.Bounds);
string expected = @"
┌─┬──────┐
│A│B │
├─┼──────┤
│1│2 │
";
TestHelpers.AssertDriverContentsAre (expected, output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TableView_ExpandLastColumn_False ()
{
var tv = SetUpMiniTable ();
// the thing we are testing
tv.Style.ExpandLastColumn = false;
tv.Redraw (tv.Bounds);
string expected = @"
┌─┬─┬────┐
│A│B│ │
├─┼─┼────┤
│1│2│ │
";
TestHelpers.AssertDriverContentsAre (expected, output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TableView_ExpandLastColumn_False_ExactBounds ()
{
var tv = SetUpMiniTable ();
// the thing we are testing
tv.Style.ExpandLastColumn = false;
// width exactly matches the max col widths
tv.Bounds = new Rect (0, 0, 5, 4);
tv.Redraw (tv.Bounds);
string expected = @"
┌─┬─┐
│A│B│
├─┼─┤
│1│2│
";
TestHelpers.AssertDriverContentsAre (expected, output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
[AutoInitShutdown]
public void TableView_Activate()
{
string activatedValue = null;
var tv = new TableView (BuildTable(1,1));
tv.CellActivated += (c) => activatedValue = c.Table.Rows[c.Row][c.Col].ToString();
Application.Top.Add (tv);
Application.Begin (Application.Top);
// pressing enter should activate the first cell (selected cell)
tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
Assert.Equal ("R0C0",activatedValue);
// reset the test
activatedValue = null;
// clear keybindings and ensure that Enter does not trigger the event anymore
tv.ClearKeybindings ();
tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
Assert.Null(activatedValue);
// New method for changing the activation key
tv.AddKeyBinding (Key.z, Command.Accept);
tv.ProcessKey (new KeyEvent (Key.z, new KeyModifiers ()));
Assert.Equal ("R0C0", activatedValue);
// reset the test
activatedValue = null;
tv.ClearKeybindings ();
// Old method for changing the activation key
tv.CellActivationKey = Key.z;
tv.ProcessKey (new KeyEvent (Key.z, new KeyModifiers ()));
Assert.Equal ("R0C0", activatedValue);
}
[Fact]
public void TableViewMultiSelect_CannotFallOffLeft()
{
var tv = SetUpMiniTable ();
tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
tv.MultiSelect = true;
tv.SelectedColumn = 1;
tv.SelectedRow = 1;
tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single().Rect);
// this next shift left should be ignored because we are already at the bounds
tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
Assert.Equal (0, tv.SelectedColumn);
Assert.Equal (1, tv.SelectedRow);
Application.Shutdown ();
}
[Fact]
public void TableViewMultiSelect_CannotFallOffRight()
{
var tv = SetUpMiniTable ();
tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
tv.MultiSelect = true;
tv.SelectedColumn = 0;
tv.SelectedRow = 1;
tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
// this next shift right should be ignored because we are already at the right bounds
tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
Assert.Equal (1, tv.SelectedColumn);
Assert.Equal (1, tv.SelectedRow);
Application.Shutdown ();
}
[Fact]
public void TableViewMultiSelect_CannotFallOffBottom ()
{
var tv = SetUpMiniTable ();
tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
tv.MultiSelect = true;
tv.SelectedColumn = 0;
tv.SelectedRow = 0;
tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
tv.ProcessKey (new KeyEvent (Key.CursorDown | Key.ShiftMask, new KeyModifiers { Shift = true }));
Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
// this next moves should be ignored because we already selected the whole table
tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
tv.ProcessKey (new KeyEvent (Key.CursorDown | Key.ShiftMask, new KeyModifiers { Shift = true }));
Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
Assert.Equal (1, tv.SelectedColumn);
Assert.Equal (1, tv.SelectedRow);
Application.Shutdown ();
}
[Fact]
public void TableViewMultiSelect_CannotFallOffTop()
{
var tv = SetUpMiniTable ();
tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
tv.MultiSelect = true;
tv.SelectedColumn = 1;
tv.SelectedRow = 1;
tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
tv.ProcessKey (new KeyEvent (Key.CursorUp | Key.ShiftMask, new KeyModifiers { Shift = true }));
Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
// this next moves should be ignored because we already selected the whole table
tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
tv.ProcessKey (new KeyEvent (Key.CursorUp | Key.ShiftMask, new KeyModifiers { Shift = true }));
Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
Assert.Equal (0, tv.SelectedColumn);
Assert.Equal (0, tv.SelectedRow);
Application.Shutdown ();
}
[Fact, AutoInitShutdown]
public void TestShiftClick_MultiSelect_TwoRowTable_FullRowSelect()
{
var tv = GetTwoRowSixColumnTable ();
tv.MultiSelect = true;
// Clicking in bottom row
tv.MouseEvent (new MouseEvent {
X = 1,
Y = 3,
Flags = MouseFlags.Button1Clicked
});
// should select that row
Assert.Equal (1, tv.SelectedRow);
// shift clicking top row
tv.MouseEvent (new MouseEvent {
X = 1,
Y = 2,
Flags = MouseFlags.Button1Clicked | MouseFlags.ButtonShift
});
// should extend the selection
Assert.Equal (0, tv.SelectedRow);
var selected = tv.GetAllSelectedCells ().ToArray();
Assert.Contains (new Point(0,0), selected);
Assert.Contains (new Point (0, 1), selected);
}
[Fact, AutoInitShutdown]
public void TestControlClick_MultiSelect_ThreeRowTable_FullRowSelect ()
{
var tv = GetTwoRowSixColumnTable ();
tv.Table.Rows.Add (1, 2, 3, 4, 5, 6);
tv.MultiSelect = true;
// Clicking in bottom row
tv.MouseEvent (new MouseEvent {
X = 1,
Y = 4,
Flags = MouseFlags.Button1Clicked
});
// should select that row
Assert.Equal (2, tv.SelectedRow);
// shift clicking top row
tv.MouseEvent (new MouseEvent {
X = 1,
Y = 2,
Flags = MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl
});
// should extend the selection
// to include bottom and top row but not middle
Assert.Equal (0, tv.SelectedRow);
var selected = tv.GetAllSelectedCells ().ToArray ();
Assert.Contains (new Point (0, 0), selected);
Assert.DoesNotContain (new Point (0, 1), selected);
Assert.Contains (new Point (0, 2), selected);
}
[Theory]
[InlineData (false)]
[InlineData (true)]
public void TableView_ColorTests_FocusedOrNot (bool focused)
{
var tv = SetUpMiniTable ();
// width exactly matches the max col widths
tv.Bounds = new Rect (0, 0, 5, 4);
// private method for forcing the view to be focused/not focused
var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
// when the view is/isn't focused
setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
tv.Redraw (tv.Bounds);
string expected = @"
┌─┬─┐
│A│B│
├─┼─┤
│1│2│
";
TestHelpers.AssertDriverContentsAre (expected, output);
string expectedColors = @"
00000
00000
00000
01000
";
TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
// 0
tv.ColorScheme.Normal,
// 1
focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal});
Application.Shutdown();
}
[Theory]
[InlineData (false)]
[InlineData (true)]
public void TableView_ColorTests_InvertSelectedCellFirstCharacter (bool focused)
{
var tv = SetUpMiniTable ();
tv.Style.InvertSelectedCellFirstCharacter = true;
// width exactly matches the max col widths
tv.Bounds = new Rect (0, 0, 5, 4);
// private method for forcing the view to be focused/not focused
var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
// when the view is/isn't focused
setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
tv.Redraw (tv.Bounds);
string expected = @"
┌─┬─┐
│A│B│
├─┼─┤
│1│2│
";
TestHelpers.AssertDriverContentsAre (expected, output);
string expectedColors = @"
00000
00000
00000
01000
";
var invertHotFocus = new Attribute(tv.ColorScheme.HotFocus.Background,tv.ColorScheme.HotFocus.Foreground);
var invertHotNormal = new Attribute(tv.ColorScheme.HotNormal.Background,tv.ColorScheme.HotNormal.Foreground);
TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
// 0
tv.ColorScheme.Normal,
// 1
focused ? invertHotFocus : invertHotNormal});
Application.Shutdown();
}
[Theory]
[InlineData (false)]
[InlineData (true)]
public void TableView_ColorsTest_RowColorGetter (bool focused)
{
var tv = SetUpMiniTable ();
// width exactly matches the max col widths
tv.Bounds = new Rect (0, 0, 5, 4);
var rowHighlight = new ColorScheme () {
Normal = Attribute.Make (Color.BrightCyan, Color.DarkGray),
HotNormal = Attribute.Make (Color.Green, Color.Blue),
HotFocus = Attribute.Make (Color.BrightYellow, Color.White),
Focus = Attribute.Make (Color.Cyan, Color.Magenta),
};
// when B is 2 use the custom highlight colour for the row
tv.Style.RowColorGetter += (e)=>Convert.ToInt32(e.Table.Rows[e.RowIndex][1]) == 2 ? rowHighlight : null;
// private method for forcing the view to be focused/not focused
var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
// when the view is/isn't focused
setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
tv.Redraw (tv.Bounds);
string expected = @"
┌─┬─┐
│A│B│
├─┼─┤
│1│2│
";
TestHelpers.AssertDriverContentsAre (expected, output);
string expectedColors = @"
00000
00000
00000
21222
";
TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
// 0
tv.ColorScheme.Normal,
// 1
focused ? rowHighlight.HotFocus : rowHighlight.HotNormal,
// 2
rowHighlight.Normal});
// change the value in the table so that
// it no longer matches the RowColorGetter
// delegate conditional ( which checks for
// the value 2)
tv.Table.Rows[0][1] = 5;
tv.Redraw (tv.Bounds);
expected = @"
┌─┬─┐
│A│B│
├─┼─┤
│1│5│
";
TestHelpers.AssertDriverContentsAre (expected, output);
expectedColors = @"
00000
00000
00000
01000
";
// now we only see 2 colors used (the selected cell color and Normal
// rowHighlight should no longer be used because the delegate returned null
// (now that the cell value is 5 - which does not match the conditional)
TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
// 0
tv.ColorScheme.Normal,
// 1
focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal });
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Theory]
[InlineData (false)]
[InlineData (true)]
public void TableView_ColorsTest_ColorGetter (bool focused)
{
var tv = SetUpMiniTable ();
// width exactly matches the max col widths
tv.Bounds = new Rect (0, 0, 5, 4);
// Create a style for column B
var bStyle = tv.Style.GetOrCreateColumnStyle (tv.Table.Columns ["B"]);
// when B is 2 use the custom highlight colour
var cellHighlight = new ColorScheme () {
Normal = Attribute.Make (Color.BrightCyan, Color.DarkGray),
HotNormal = Attribute.Make (Color.Green, Color.Blue),
HotFocus = Attribute.Make (Color.BrightYellow, Color.White),
Focus = Attribute.Make (Color.Cyan, Color.Magenta),
};
bStyle.ColorGetter = (a) => Convert.ToInt32(a.CellValue) == 2 ? cellHighlight : null;
// private method for forcing the view to be focused/not focused
var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
// when the view is/isn't focused
setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
tv.Redraw (tv.Bounds);
string expected = @"
┌─┬─┐
│A│B│
├─┼─┤
│1│2│
";
TestHelpers.AssertDriverContentsAre (expected, output);
string expectedColors = @"
00000
00000
00000
01020
";
TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
// 0
tv.ColorScheme.Normal,
// 1
focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal,
// 2
cellHighlight.Normal});
// change the value in the table so that
// it no longer matches the ColorGetter
// delegate conditional ( which checks for
// the value 2)
tv.Table.Rows[0][1] = 5;
tv.Redraw (tv.Bounds);
expected = @"
┌─┬─┐
│A│B│
├─┼─┤
│1│5│
";
TestHelpers.AssertDriverContentsAre (expected, output);
expectedColors = @"
00000
00000
00000
01000
";
// now we only see 2 colors used (the selected cell color and Normal
// cellHighlight should no longer be used because the delegate returned null
// (now that the cell value is 5 - which does not match the conditional)
TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
// 0
tv.ColorScheme.Normal,
// 1
focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal });
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
private TableView SetUpMiniTable ()
{
var tv = new TableView ();
tv.Bounds = new Rect (0, 0, 10, 4);
var dt = new DataTable ();
var colA = dt.Columns.Add ("A");
var colB = dt.Columns.Add ("B");
dt.Rows.Add (1, 2);
tv.Table = dt;
tv.Style.GetOrCreateColumnStyle (colA).MinWidth = 1;
tv.Style.GetOrCreateColumnStyle (colA).MinWidth = 1;
tv.Style.GetOrCreateColumnStyle (colB).MaxWidth = 1;
tv.Style.GetOrCreateColumnStyle (colB).MaxWidth = 1;
GraphViewTests.InitFakeDriver ();
tv.ColorScheme = Colors.Base;
return tv;
}
[Fact]
[AutoInitShutdown]
public void ScrollDown_OneLineAtATime ()
{
var tableView = new TableView ();
// Set big table
tableView.Table = BuildTable (25, 50);
// 1 header + 4 rows visible
tableView.Bounds = new Rect (0, 0, 25, 5);
tableView.Style.ShowHorizontalHeaderUnderline = false;
tableView.Style.ShowHorizontalHeaderOverline = false;
tableView.Style.AlwaysShowHeaders = true;
// select last row
tableView.SelectedRow = 3; // row is 0 indexed so this is the 4th visible row
// Scroll down
tableView.ProcessKey (new KeyEvent () { Key = Key.CursorDown });
// Scrolled off the page by 1 row so it should only have moved down 1 line of RowOffset
Assert.Equal(4,tableView.SelectedRow);
Assert.Equal (1, tableView.RowOffset);
}
[Fact]
public void ScrollRight_SmoothScrolling ()
{
GraphViewTests.InitFakeDriver ();
var tableView = new TableView ();
tableView.ColorScheme = Colors.TopLevel;
// 3 columns are visibile
tableView.Bounds = new Rect (0, 0, 7, 5);
tableView.Style.ShowHorizontalHeaderUnderline = false;
tableView.Style.ShowHorizontalHeaderOverline = false;
tableView.Style.AlwaysShowHeaders = true;
tableView.Style.SmoothHorizontalScrolling = true;
var dt = new DataTable ();
dt.Columns.Add ("A");
dt.Columns.Add ("B");
dt.Columns.Add ("C");
dt.Columns.Add ("D");
dt.Columns.Add ("E");
dt.Columns.Add ("F");
dt.Rows.Add (1, 2, 3, 4, 5, 6);
tableView.Table = dt;
// select last visible column
tableView.SelectedColumn = 2; // column C
tableView.Redraw (tableView.Bounds);
string expected =
@"
│A│B│C│
│1│2│3│";
TestHelpers.AssertDriverContentsAre (expected, output);
// Scroll right
tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
tableView.Redraw (tableView.Bounds);
// Note that with SmoothHorizontalScrolling only a single new column
// is exposed when scrolling right. This is not always the case though
// sometimes if the leftmost column is long (i.e. A is a long column)
// then when A is pushed off the screen multiple new columns could be exposed
// (not just D but also E and F). This is because TableView never shows
// 'half cells' or scrolls by console unit (scrolling is done by table row/column increments).
expected =
@"
│B│C│D│
│2│3│4│";
TestHelpers.AssertDriverContentsAre (expected, output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void ScrollRight_WithoutSmoothScrolling ()
{
GraphViewTests.InitFakeDriver ();
var tableView = new TableView ();
tableView.ColorScheme = Colors.TopLevel;
// 3 columns are visibile
tableView.Bounds = new Rect (0, 0, 7, 5);
tableView.Style.ShowHorizontalHeaderUnderline = false;
tableView.Style.ShowHorizontalHeaderOverline = false;
tableView.Style.AlwaysShowHeaders = true;
tableView.Style.SmoothHorizontalScrolling = false;
var dt = new DataTable ();
dt.Columns.Add ("A");
dt.Columns.Add ("B");
dt.Columns.Add ("C");
dt.Columns.Add ("D");
dt.Columns.Add ("E");
dt.Columns.Add ("F");
dt.Rows.Add (1, 2, 3, 4, 5, 6);
tableView.Table = dt;
// select last visible column
tableView.SelectedColumn = 2; // column C
tableView.Redraw (tableView.Bounds);
string expected =
@"
│A│B│C│
│1│2│3│";
TestHelpers.AssertDriverContentsAre (expected, output);
// Scroll right
tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
tableView.Redraw (tableView.Bounds);
// notice that without smooth scrolling we just update the first column
// rendered in the table to the newly exposed column (D). This is fast
// since we don't have to worry about repeatedly measuring the content
// area as we scroll until the new column (D) is exposed. But it makes
// the view 'jump' to expose all new columns
expected =
@"
│D│E│F│
│4│5│6│";
TestHelpers.AssertDriverContentsAre (expected, output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
private TableView GetABCDEFTableView (out DataTable dt)
{
var tableView = new TableView ();
tableView.ColorScheme = Colors.TopLevel;
// 3 columns are visible
tableView.Bounds = new Rect (0, 0, 7, 5);
tableView.Style.ShowHorizontalHeaderUnderline = false;
tableView.Style.ShowHorizontalHeaderOverline = false;
tableView.Style.AlwaysShowHeaders = true;
tableView.Style.SmoothHorizontalScrolling = false;
dt = new DataTable ();
dt.Columns.Add ("A");
dt.Columns.Add ("B");
dt.Columns.Add ("C");
dt.Columns.Add ("D");
dt.Columns.Add ("E");
dt.Columns.Add ("F");
dt.Rows.Add (1, 2, 3, 4, 5, 6);
tableView.Table = dt;
return tableView;
}
[Fact, AutoInitShutdown]
public void TestColumnStyle_VisibleFalse_IsNotRendered()
{
var tableView = GetABCDEFTableView (out DataTable dt);
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
tableView.Redraw (tableView.Bounds);
string expected =
@"
│A│C│D│
│1│3│4│";
TestHelpers.AssertDriverContentsAre (expected, output);
}
[Fact, AutoInitShutdown]
public void TestColumnStyle_FirstColumnVisibleFalse_IsNotRendered ()
{
var tableView = GetABCDEFTableView (out DataTable dt);
tableView.Style.ShowHorizontalScrollIndicators = true;
tableView.Style.ShowHorizontalHeaderUnderline = true;
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
tableView.Redraw (tableView.Bounds);
string expected =
@"
│B│C│D│
├─┼─┼─►
│2│3│4│";
TestHelpers.AssertDriverContentsAre (expected, output);
}
[Fact, AutoInitShutdown]
public void TestColumnStyle_AllColumnsVisibleFalse_BehavesAsTableNull ()
{
var tableView = GetABCDEFTableView (out DataTable dt);
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["C"]).Visible = false;
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
// expect nothing to be rendered when all columns are invisible
string expected =
@"
";
tableView.Redraw (tableView.Bounds);
TestHelpers.AssertDriverContentsAre (expected, output);
// expect behavior to match when Table is null
tableView.Table = null;
tableView.Redraw (tableView.Bounds);
TestHelpers.AssertDriverContentsAre (expected, output);
}
[Fact, AutoInitShutdown]
public void TestColumnStyle_RemainingColumnsInvisible_NoScrollIndicator ()
{
var tableView = GetABCDEFTableView (out DataTable dt);
tableView.Style.ShowHorizontalScrollIndicators = true;
tableView.Style.ShowHorizontalHeaderUnderline = true;
tableView.Redraw (tableView.Bounds);
// normally we should have scroll indicators because DEF are of screen
string expected =
@"
│A│B│C│
├─┼─┼─►
│1│2│3│";
TestHelpers.AssertDriverContentsAre (expected, output);
// but if DEF are invisible we shouldn't be showing the indicator
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
expected =
@"
│A│B│C│
├─┼─┼─┤
│1│2│3│";
tableView.Redraw (tableView.Bounds);
TestHelpers.AssertDriverContentsAre (expected, output);
}
[Fact, AutoInitShutdown]
public void TestColumnStyle_PreceedingColumnsInvisible_NoScrollIndicator ()
{
var tableView = GetABCDEFTableView (out DataTable dt);
tableView.Style.ShowHorizontalScrollIndicators = true;
tableView.Style.ShowHorizontalHeaderUnderline = true;
tableView.ColumnOffset = 1;
tableView.Redraw (tableView.Bounds);
// normally we should have scroll indicators because A,E and F are of screen
string expected =
@"
│B│C│D│
◄─┼─┼─►
│2│3│4│";
TestHelpers.AssertDriverContentsAre (expected, output);
// but if E and F are invisible so we shouldn't show right
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
expected =
@"
│B│C│D│
◄─┼─┼─┤
│2│3│4│";
tableView.Redraw (tableView.Bounds);
TestHelpers.AssertDriverContentsAre (expected, output);
// now also A is invisible so we cannot scroll in either direction
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
expected =
@"
│B│C│D│
├─┼─┼─┤
│2│3│4│";
tableView.Redraw (tableView.Bounds);
TestHelpers.AssertDriverContentsAre (expected, output);
}
[Fact, AutoInitShutdown]
public void TestColumnStyle_VisibleFalse_CursorStepsOverInvisibleColumns ()
{
var tableView = GetABCDEFTableView (out var dt);
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
tableView.SelectedColumn = 0;
tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
// Expect the cursor navigation to skip over the invisible column(s)
Assert.Equal(2,tableView.SelectedColumn);
tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
// Expect the cursor navigation backwards to skip over invisible column too
Assert.Equal (0, tableView.SelectedColumn);
}
[InlineData(true)]
[InlineData (false)]
[Theory, AutoInitShutdown]
public void TestColumnStyle_FirstColumnVisibleFalse_CursorStaysAt1(bool useHome)
{
var tableView = GetABCDEFTableView (out var dt);
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
tableView.SelectedColumn = 0;
Assert.Equal (0, tableView.SelectedColumn);
// column 0 is invisible so this method should move to 1
tableView.EnsureValidSelection();
Assert.Equal (1, tableView.SelectedColumn);
tableView.ProcessKey (new KeyEvent
{
Key = useHome ? Key.Home : Key.CursorLeft
});
// Expect the cursor to stay at 1
Assert.Equal (1, tableView.SelectedColumn);
}
[InlineData(true)]
[InlineData (false)]
[Theory, AutoInitShutdown]
public void TestMoveStartEnd_WithFullRowSelect(bool withFullRowSelect)
{
var tableView = GetTwoRowSixColumnTable ();
tableView.FullRowSelect = withFullRowSelect;
tableView.SelectedRow = 1;
tableView.SelectedColumn = 1;
tableView.ProcessKey (new KeyEvent
{
Key = Key.Home | Key.CtrlMask
});
if(withFullRowSelect)
{
// Should not be any horizontal movement when
// using navigate to Start/End and FullRowSelect
Assert.Equal (1, tableView.SelectedColumn);
Assert.Equal (0, tableView.SelectedRow);
}
else
{
Assert.Equal (0, tableView.SelectedColumn);
Assert.Equal (0, tableView.SelectedRow);
}
tableView.ProcessKey (new KeyEvent
{
Key = Key.End | Key.CtrlMask
});
if(withFullRowSelect)
{
Assert.Equal (1, tableView.SelectedColumn);
Assert.Equal (1, tableView.SelectedRow);
}
else
{
Assert.Equal (5, tableView.SelectedColumn);
Assert.Equal (1, tableView.SelectedRow);
}
}
[InlineData (true)]
[InlineData (false)]
[Theory, AutoInitShutdown]
public void TestColumnStyle_LastColumnVisibleFalse_CursorStaysAt2 (bool useEnd)
{
var tableView = GetABCDEFTableView (out var dt);
// select D
tableView.SelectedColumn = 3;
Assert.Equal (3, tableView.SelectedColumn);
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
// column D is invisible so this method should move to 2 (C)
tableView.EnsureValidSelection ();
Assert.Equal (2, tableView.SelectedColumn);
tableView.ProcessKey (new KeyEvent {
Key = useEnd ? Key.End : Key.CursorRight
});
// Expect the cursor to stay at 2
Assert.Equal (2, tableView.SelectedColumn);
}
[Fact, AutoInitShutdown]
public void TestColumnStyle_VisibleFalse_MultiSelected ()
{
var tableView = GetABCDEFTableView (out var dt);
// user has rectangular selection
tableView.MultiSelectedRegions.Push (
new TableView.TableSelection(
new Point(0,0),
new Rect(0, 0, 3, 1))
);
Assert.Equal (3, tableView.GetAllSelectedCells ().Count());
Assert.True (tableView.IsSelected (0, 0));
Assert.True (tableView.IsSelected (1, 0));
Assert.True (tableView.IsSelected (2, 0));
Assert.False (tableView.IsSelected (3, 0));
// if middle column is invisible
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
// it should not be included in the selection
Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
Assert.True (tableView.IsSelected (0, 0));
Assert.False (tableView.IsSelected (1, 0));
Assert.True (tableView.IsSelected (2, 0));
Assert.False (tableView.IsSelected (3, 0));
Assert.DoesNotContain(new Point(1,0),tableView.GetAllSelectedCells ());
}
[Fact, AutoInitShutdown]
public void TestColumnStyle_VisibleFalse_MultiSelectingStepsOverInvisibleColumns ()
{
var tableView = GetABCDEFTableView (out var dt);
// if middle column is invisible
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight | Key.ShiftMask });
// Selection should extend from A to C but skip B
Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
Assert.True (tableView.IsSelected (0, 0));
Assert.False (tableView.IsSelected (1, 0));
Assert.True (tableView.IsSelected (2, 0));
Assert.False (tableView.IsSelected (3, 0));
Assert.DoesNotContain (new Point (1, 0), tableView.GetAllSelectedCells ());
}
[Theory, AutoInitShutdown]
[InlineData(new object[] { true,true })]
[InlineData (new object[] { false,true })]
[InlineData (new object [] { true, false})]
[InlineData (new object [] { false, false})]
public void TestColumnStyle_VisibleFalse_DoesNotEffect_EnsureSelectedCellIsVisible (bool smooth, bool invisibleCol)
{
var tableView = GetABCDEFTableView (out var dt);
tableView.Style.SmoothHorizontalScrolling = smooth;
if(invisibleCol) {
tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
}
// New TableView should have first cell selected
Assert.Equal (0,tableView.SelectedColumn);
// With no scrolling
Assert.Equal (0, tableView.ColumnOffset);
// A,B and C are visible on screen at the moment so these should have no effect
tableView.SelectedColumn = 1;
tableView.EnsureSelectedCellIsVisible ();
Assert.Equal (0, tableView.ColumnOffset);
tableView.SelectedColumn = 2;
tableView.EnsureSelectedCellIsVisible ();
Assert.Equal (0, tableView.ColumnOffset);
// Selecting D should move the visible table area to fit D onto the screen
tableView.SelectedColumn = 3;
tableView.EnsureSelectedCellIsVisible ();
Assert.Equal (smooth ? 1 : 3, tableView.ColumnOffset);
}
[Fact]
public void LongColumnTest ()
{
GraphViewTests.InitFakeDriver ();
var tableView = new TableView ();
tableView.ColorScheme = Colors.TopLevel;
// 25 characters can be printed into table
tableView.Bounds = new Rect (0, 0, 25, 5);
tableView.Style.ShowHorizontalHeaderUnderline = true;
tableView.Style.ShowHorizontalHeaderOverline = false;
tableView.Style.AlwaysShowHeaders = true;
tableView.Style.SmoothHorizontalScrolling = true;
var dt = new DataTable ();
dt.Columns.Add ("A");
dt.Columns.Add ("B");
dt.Columns.Add ("Very Long Column");
dt.Rows.Add (1, 2, new string('a',500));
dt.Rows.Add (1, 2, "aaa");
tableView.Table = dt;
tableView.Redraw (tableView.Bounds);
// default behaviour of TableView is not to render
// columns unless there is sufficient space
string expected =
@"
│A│B │
├─┼─────────────────────►
│1│2 │
│1│2 │
";
TestHelpers.AssertDriverContentsAre (expected, output);
// get a style for the long column
var style = tableView.Style.GetOrCreateColumnStyle(dt.Columns[2]);
// one way the API user can fix this for long columns
// is to specify a max width for the column
style.MaxWidth = 10;
tableView.Redraw (tableView.Bounds);
expected =
@"
│A│B│Very Long │
├─┼─┼───────────────────┤
│1│2│aaaaaaaaaa │
│1│2│aaa │
";
TestHelpers.AssertDriverContentsAre (expected, output);
// revert the style change
style.MaxWidth = TableView.DefaultMaxCellWidth;
// another way API user can fix problem is to implement
// RepresentationGetter and apply max length there
style.RepresentationGetter = (s)=>{
return s.ToString().Length < 15 ? s.ToString() : s.ToString().Substring(0,13)+"...";
};
tableView.Redraw (tableView.Bounds);
expected =
@"
│A│B│Very Long Column │
├─┼─┼───────────────────┤
│1│2│aaaaaaaaaaaaa... │
│1│2│aaa │
";
TestHelpers.AssertDriverContentsAre (expected, output);
// revert style change
style.RepresentationGetter = null;
// Both of the above methods rely on having a fixed
// size limit for the column. These are awkward if a
// table is resizeable e.g. Dim.Fill(). Ideally we want
// to render in any space available and truncate the content
// of the column dynamically so it fills the free space at
// the end of the table.
// We can now specify that the column can be any length
// (Up to MaxWidth) but the renderer can accept using
// less space down to this limit
style.MinAcceptableWidth = 5;
tableView.Redraw (tableView.Bounds);
expected =
@"
│A│B│Very Long Column │
├─┼─┼───────────────────┤
│1│2│aaaaaaaaaaaaaaaaaaa│
│1│2│aaa │
";
TestHelpers.AssertDriverContentsAre (expected, output);
// Now test making the width too small for the MinAcceptableWidth
// the Column won't fit so should not be rendered
Application.Shutdown ();
GraphViewTests.InitFakeDriver ();
tableView.Bounds = new Rect(0,0,9,5);
tableView.Redraw (tableView.Bounds);
expected =
@"
│A│B │
├─┼─────►
│1│2 │
│1│2 │
";
TestHelpers.AssertDriverContentsAre (expected, output);
// setting width to 10 leaves just enough space for the column to
// meet MinAcceptableWidth of 5. Column width includes terminator line
// symbol (e.g. ┤ or │)
tableView.Bounds = new Rect (0, 0, 10, 5);
tableView.Redraw (tableView.Bounds);
expected =
@"
│A│B│Very│
├─┼─┼────┤
│1│2│aaaa│
│1│2│aaa │
";
TestHelpers.AssertDriverContentsAre (expected, output);
Application.Shutdown ();
}
[Fact]
public void ScrollIndicators ()
{
GraphViewTests.InitFakeDriver ();
var tableView = new TableView ();
tableView.ColorScheme = Colors.TopLevel;
// 3 columns are visibile
tableView.Bounds = new Rect (0, 0, 7, 5);
tableView.Style.ShowHorizontalHeaderUnderline = true;
tableView.Style.ShowHorizontalHeaderOverline = false;
tableView.Style.AlwaysShowHeaders = true;
tableView.Style.SmoothHorizontalScrolling = true;
var dt = new DataTable ();
dt.Columns.Add ("A");
dt.Columns.Add ("B");
dt.Columns.Add ("C");
dt.Columns.Add ("D");
dt.Columns.Add ("E");
dt.Columns.Add ("F");
dt.Rows.Add (1, 2, 3, 4, 5, 6);
tableView.Table = dt;
// select last visible column
tableView.SelectedColumn = 2; // column C
tableView.Redraw (tableView.Bounds);
// user can only scroll right so sees right indicator
// Because first column in table is A
string expected =
@"
│A│B│C│
├─┼─┼─►
│1│2│3│";
TestHelpers.AssertDriverContentsAre (expected, output);
// Scroll right
tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
// since A is now pushed off screen we get indicator showing
// that user can scroll left to see first column
tableView.Redraw (tableView.Bounds);
expected =
@"
│B│C│D│
◄─┼─┼─►
│2│3│4│";
TestHelpers.AssertDriverContentsAre (expected, output);
// Scroll right twice more (to end of columns)
tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
tableView.Redraw (tableView.Bounds);
expected =
@"
│D│E│F│
◄─┼─┼─┤
│4│5│6│";
TestHelpers.AssertDriverContentsAre (expected, output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
///
/// Builds a simple table of string columns with the requested number of columns and rows
///
///
///
///
public static DataTable BuildTable (int cols, int rows)
{
var dt = new DataTable ();
for (int c = 0; c < cols; c++) {
dt.Columns.Add ("Col" + c);
}
for (int r = 0; r < rows; r++) {
var newRow = dt.NewRow ();
for (int c = 0; c < cols; c++) {
newRow [c] = $"R{r}C{c}";
}
dt.Rows.Add (newRow);
}
return dt;
}
[Fact, AutoInitShutdown]
public void Test_ScreenToCell ()
{
var tableView = GetTwoRowSixColumnTable ();
tableView.Redraw (tableView.Bounds);
// user can only scroll right so sees right indicator
// Because first column in table is A
string expected =
@"
│A│B│C│
├─┼─┼─►
│1│2│3│
│1│2│3│";
TestHelpers.AssertDriverContentsAre (expected, output);
// ---------------- X=0 -----------------------
// click is before first cell
Assert.Null (tableView.ScreenToCell (0, 0));
Assert.Null (tableView.ScreenToCell (0, 1));
Assert.Null (tableView.ScreenToCell (0, 2));
Assert.Null (tableView.ScreenToCell (0, 3));
Assert.Null (tableView.ScreenToCell (0, 4));
// ---------------- X=1 -----------------------
// click in header
Assert.Null (tableView.ScreenToCell (1, 0));
// click in header row line
Assert.Null (tableView.ScreenToCell (1, 1));
// click in cell 0,0
Assert.Equal (new Point(0,0),tableView.ScreenToCell (1, 2));
// click in cell 0,1
Assert.Equal (new Point (0, 1), tableView.ScreenToCell (1, 3));
// after last row
Assert.Null (tableView.ScreenToCell (1, 4));
// ---------------- X=2 -----------------------
// ( even though there is a horizontal dividing line here we treat it as a hit on the cell before)
// click in header
Assert.Null (tableView.ScreenToCell (2, 0));
// click in header row line
Assert.Null (tableView.ScreenToCell (2, 1));
// click in cell 0,0
Assert.Equal (new Point (0, 0), tableView.ScreenToCell (2, 2));
// click in cell 0,1
Assert.Equal (new Point (0, 1), tableView.ScreenToCell (2, 3));
// after last row
Assert.Null (tableView.ScreenToCell (2, 4));
// ---------------- X=3 -----------------------
// click in header
Assert.Null (tableView.ScreenToCell (3, 0));
// click in header row line
Assert.Null (tableView.ScreenToCell (3, 1));
// click in cell 1,0
Assert.Equal (new Point (1, 0), tableView.ScreenToCell (3, 2));
// click in cell 1,1
Assert.Equal (new Point (1, 1), tableView.ScreenToCell (3, 3));
// after last row
Assert.Null (tableView.ScreenToCell (3, 4));
}
[Fact, AutoInitShutdown]
public void Test_ScreenToCell_DataColumnOverload ()
{
var tableView = GetTwoRowSixColumnTable ();
tableView.Redraw (tableView.Bounds);
// user can only scroll right so sees right indicator
// Because first column in table is A
string expected =
@"
│A│B│C│
├─┼─┼─►
│1│2│3│
│1│2│3│";
TestHelpers.AssertDriverContentsAre (expected, output);
DataColumn col;
// ---------------- X=0 -----------------------
// click is before first cell
Assert.Null (tableView.ScreenToCell (0, 0,out col));
Assert.Null (col);
Assert.Null (tableView.ScreenToCell (0, 1,out col));
Assert.Null (col);
Assert.Null (tableView.ScreenToCell (0, 2,out col));
Assert.Null (col);
Assert.Null (tableView.ScreenToCell (0, 3,out col));
Assert.Null (col);
Assert.Null (tableView.ScreenToCell (0, 4,out col));
Assert.Null (col);
// ---------------- X=1 -----------------------
// click in header
Assert.Null (tableView.ScreenToCell (1, 0, out col));
Assert.Equal ("A", col.ColumnName);
// click in header row line (click in the horizontal line below header counts as click in header above - consistent with the column hit box)
Assert.Null (tableView.ScreenToCell (1, 1, out col));
Assert.Equal ("A", col.ColumnName);
// click in cell 0,0
Assert.Equal (new Point (0, 0), tableView.ScreenToCell (1, 2, out col));
Assert.Null (col);
// click in cell 0,1
Assert.Equal (new Point (0, 1), tableView.ScreenToCell (1, 3, out col));
Assert.Null (col);
// after last row
Assert.Null (tableView.ScreenToCell (1, 4, out col));
Assert.Null (col);
// ---------------- X=2 -----------------------
// click in header
Assert.Null (tableView.ScreenToCell (2, 0, out col));
Assert.Equal ("A", col.ColumnName);
// click in header row line
Assert.Null (tableView.ScreenToCell (2, 1, out col));
Assert.Equal ("A", col.ColumnName);
// click in cell 0,0
Assert.Equal (new Point (0, 0), tableView.ScreenToCell (2, 2, out col));
Assert.Null (col);
// click in cell 0,1
Assert.Equal (new Point (0, 1), tableView.ScreenToCell (2, 3, out col));
Assert.Null (col);
// after last row
Assert.Null (tableView.ScreenToCell (2, 4, out col));
Assert.Null (col);
// ---------------- X=3 -----------------------
// click in header
Assert.Null (tableView.ScreenToCell (3, 0, out col));
Assert.Equal ("B", col.ColumnName);
// click in header row line
Assert.Null (tableView.ScreenToCell (3, 1, out col));
Assert.Equal ("B", col.ColumnName);
// click in cell 1,0
Assert.Equal (new Point (1, 0), tableView.ScreenToCell (3, 2, out col));
Assert.Null (col);
// click in cell 1,1
Assert.Equal (new Point (1, 1), tableView.ScreenToCell (3, 3, out col));
Assert.Null (col);
// after last row
Assert.Null (tableView.ScreenToCell (3, 4, out col));
Assert.Null (col);
}
private TableView GetTwoRowSixColumnTable ()
{
var tableView = new TableView ();
tableView.ColorScheme = Colors.TopLevel;
// 3 columns are visible
tableView.Bounds = new Rect (0, 0, 7, 5);
tableView.Style.ShowHorizontalHeaderUnderline = true;
tableView.Style.ShowHorizontalHeaderOverline = false;
tableView.Style.AlwaysShowHeaders = true;
tableView.Style.SmoothHorizontalScrolling = true;
var dt = new DataTable ();
dt.Columns.Add ("A");
dt.Columns.Add ("B");
dt.Columns.Add ("C");
dt.Columns.Add ("D");
dt.Columns.Add ("E");
dt.Columns.Add ("F");
dt.Rows.Add (1, 2, 3, 4, 5, 6);
dt.Rows.Add (1, 2, 3, 4, 5, 6);
tableView.Table = dt;
return tableView;
}
}
}