mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* Empty GraphView with basic axis * Added ISeries * Added zoom * Fixed zoom * Tests and scrolling * Refactored AxisView into abstract base * Added atomic mass example * Added Y axis labels * Added Y axis labels * comments * Refactored axis to not be floating views * Split axis drawing code to seperate draw line from draw labels * Added MarginBottom and MarginLeft * Added bar graph * Fixes horizontal axis label generation * Fixed axis labels changing during scrolling * Added test for overlapping cells * Added TestReversing_ScreenToGraphSpace * Changed graph space from float to decimal * Added axis labels * Fixed issues where labels/axis overspilled bounds * Fixed origin screen coordinates being off by 1 in y axis * Added Orientation to BarSeries * Added comments and standardised Name to Text * Added prototype 'population pyramid' * Fixed bar graphs not stopping at axis * Added Reset and Ctrl to speed up scrolling * Added line graph * Fixed LineSeries implementation * Made LineSeries Points readonly and sort on add * Fixed RectangleD.GetHasCode() * Improved performance of LineSeries * Added color to graph * Fixed colors not working on linux * Added Visible and ColorGetter * Added Ctrl+G Next Graph * Added MultiBarSeries * Fixed layout issue with population pyramid * fixed y label overspill and origin rendering * Fixed warnings * Made examples prettier * Fixed xAxis potentially drawing labels outside of control area * Fixed multi bar example labels * Added IAnnotation * Added example of using GraphPosition in IAnnotation * Fixed Annotations drawing outside of graph bounds * Fixed Reset() not clearing Annotations and sp fixes * Changed line drawing to Bresenham's line algorithm and deleted CohenSutherland Testing for collisions with screen space is very slow and gives quite thick lines. I looked at Xiaolin Wu which supports anti aliasing but this also would require more work to look good (out of the box it just looks thick). * Fixed layout/whitespace * Graph now renders without series if annotations are present * Fixed ScreenToGraphSpace rect overload * Added SeriesDrawMode for when it is easier/faster for a series to draw itself all in one go * Added LegendAnnotation * Added tests for correct bounds * Added more tests * Changed GraphView namespace to Terminal.Gui.Graphs * Made Line2D and Horizontal/Vertical axis private classes * Made AxisIncrementToRender.Text internal to avoid confusing user when implementing `LabelGetterDelegate` * Changed back from decimal to float * Refactored axis label drawing to avoid rounding errors * Fixed over spilling bounds when drawing bars/axis increments * Re-implemented disco colors * Added Minimum to Axis * Fixed tests build and render order * Fixed test by adjusting epsilon * tidyup, docs and warning fixes * Standardised parameter order and added axis test * Fixed x axis line drawing into margins and added tests * Fixed axis increment rendering in margins, tests and tidyup examples * Added test for BarSeries * Added more BarSeriesTests * Split GraphView.cs into sub files as suggested * Fixed pointlessly passing around ConsoleDriver and Bounds * Fixed colored bars not reseting color before drawing labels * spelling fixes * Replaced System.Drawing with code copied from dotnet/corefx repo * Change to trigger CI * Added tests for MultiBarSeries * Added test support for Asserting exact graph contents * Added xml doc where missing from System.Drawing Types * Standardised unit test namespaces to Terminal.Gui * Fixed namespace correctly this time after merging main * Fixed test to avoid using Attribute constructor * Reduced code duplication in test by moving InitFakeDriver to static in GraphViewTests * Added TextAnnotationTests and improved GraphViewTests.AssertDriverContentsAre * Added more TextAnnotation tests and fixed file indentation * Added tests for Legend and Path And fixed TruncateOrPad being off by 1 when truncating * Removed unused paths in TruncateOrPad
448 lines
14 KiB
C#
448 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Terminal.Gui;
|
|
using Xunit;
|
|
using System.Globalization;
|
|
|
|
namespace Terminal.Gui.Views {
|
|
|
|
public class TableViewTests
|
|
{
|
|
|
|
[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]
|
|
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));
|
|
}
|
|
|
|
[Fact]
|
|
public void PageDown_ExcludesHeaders()
|
|
{
|
|
|
|
var driver = new FakeDriver ();
|
|
Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
|
driver.Init (() => { });
|
|
|
|
|
|
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;
|
|
|
|
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.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.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]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds a simple table of string columns with the requested number of columns and rows
|
|
/// </summary>
|
|
/// <param name="cols"></param>
|
|
/// <param name="rows"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
}
|
|
} |