Files
Terminal.Gui/UnitTests/Views/GraphViewTests.cs
dodexahedron 0f9e6f091d Stage 1 of #3256 - Prepare Rect for removal (#3260)
* Replace all 342 `== null` with `is null`

* Replace 354 `!= null` with `is { }`

* Wrap these in conditionals since they break tests against Release configuration

The members they depend on do not exist in Release configuration

* Split these up and dispose properly

This test needs to be revisited for several reasons at some point.

* Fix release configuration tests

* Declare interface these already support

* Annotate constructor properly and use throw helper

* Move class to its own file

* Rename these files so they nest in the solution explorer

* Make this a record type and remove now-redundant/illegal members

* Reference passing to avoid some struct copies

* Simplify this

* Carry reference passing through as appropriate

* Turn this into a record struct

* Remove unused internal constructor and its test

It was only used by that test.

* Simplify this constructor

* This should be a property

* Simplify constructor

* Simplify GetHashCode

* Mark this ignored just in case

* Missed a couple of opportunities for reference passing

* record struct already does this by value

* Remove unused class

* Simplify the type initializer and Reset method

* Implement INotifyCollectionChanged and IDictionary by delegating to ColorSchemes

* Fix for reflection-based configuration

* Make CI  build happy by disambiguiating this attribute

* Add PERF, NOTE, QUESTION, and CONCURRENCY tags for the todo explorer

* Make this string comparison faster.

* Add a tag for unclear intent

* This is a constant

* Turn this into a constant via use of a unicode literal

* Remove this method and its test

It is unused
There's no guarantee at all that the parent process is the terminal.
There are good reasons, including that one, why there's no simple way to do it in .net.
It's also of course a windows-only thing, if using WMI.

* With the WMI method gone, we no longer need this

* Make this more efficient

* Add detail to this property's XmlDoc

* Move the general properties up top because order matters

* Make sure any constants defined at higher levels are not clobbered and define a couple more

* Put InternalsVisibleTo in its own group

* Sort dependencies alphabetically and update

* Global usings

* Split to one type per file

* Collection expression

* Fix naming

* Inline to avoid copies

* This is already a value copy (struct)

* Combine to one non-destructive mutation

* Avoid some potential boxing

* Turn on null analysis here

* Remove unnecessary cast and use real type name

* Seal this

* Fix name

* Move nested class to a nested file (no type layout change made)

* Undo naming change that isn't changed globally until next batch

* Rename Rect to Rectangle in preparation for removal

* Add baseline test for ToString checking for current behavior.

* Change to behavior matching System.Drawing.Rectangle

* Fix this test

This is not a test of Rectangle, so trust that Rectangle gets it right.

* Fix these tests the same way as the previous commit

* These should be testing against the Rectangles, not the strings

* Slightly de-couple these as well

* Test against Rectangles, not strings

* Collection expressions and constants

* Remove this

* Perform proper platform-agnostic normalization

* Make this easier to follow (naming only)

* Add a category to this

* Use raw strings for better clarity

* Some more categorization

* Re-apply backed-out naming change from parent branch

* Change GetHashCode to be equivalent to System.Drawing.Rectangle

* Update this since 6.0.0 is no longer available and prevents build

* This check is redundant with the rectangle check below

* Re-apply Rect->Rectangle name changes in these files
2024-02-22 16:46:04 -07:00

1643 lines
52 KiB
C#

using System.Text;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewsTests;
#region Helper Classes
internal class FakeHAxis : HorizontalAxis
{
public List<Point> DrawAxisLinePoints = new ();
public List<int> LabelPoints = new ();
public override void DrawAxisLabel (GraphView graph, int screenPosition, string text)
{
base.DrawAxisLabel (graph, screenPosition, text);
LabelPoints.Add (screenPosition);
}
protected override void DrawAxisLine (GraphView graph, int x, int y)
{
base.DrawAxisLine (graph, x, y);
DrawAxisLinePoints.Add (new Point (x, y));
}
}
internal class FakeVAxis : VerticalAxis
{
public List<Point> DrawAxisLinePoints = new ();
public List<int> LabelPoints = new ();
public override void DrawAxisLabel (GraphView graph, int screenPosition, string text)
{
base.DrawAxisLabel (graph, screenPosition, text);
LabelPoints.Add (screenPosition);
}
protected override void DrawAxisLine (GraphView graph, int x, int y)
{
base.DrawAxisLine (graph, x, y);
DrawAxisLinePoints.Add (new Point (x, y));
}
}
#endregion
public class GraphViewTests
{
private static string LastInitFakeDriver;
/// <summary>
/// A cell size of 0 would result in mapping all graph space into the same cell of the console. Since
/// <see cref="GraphView.CellSize"/> is mutable a sensible place to check this is in redraw.
/// </summary>
[Fact]
public void CellSizeZero ()
{
InitFakeDriver ();
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.ColorScheme = new ColorScheme ();
gv.Bounds = new Rectangle (0, 0, 50, 30);
gv.Series.Add (new ScatterSeries { Points = new List<PointF> { new (1, 1) } });
gv.CellSize = new PointF (0, 5);
var ex = Assert.Throws<Exception> (() => gv.Draw ());
Assert.Equal ("CellSize cannot be 0", ex.Message);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
/// <summary>Returns a basic very small graph (10 x 5)</summary>
/// <returns></returns>
public static GraphView GetGraph ()
{
InitFakeDriver ();
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.ColorScheme = new ColorScheme ();
gv.MarginBottom = 1;
gv.MarginLeft = 1;
gv.Bounds = new Rectangle (0, 0, 10, 5);
return gv;
}
public static FakeDriver InitFakeDriver ()
{
var driver = new FakeDriver ();
try
{
Application.Init (driver);
}
catch (InvalidOperationException)
{
// close it so that we don't get a thousand of these errors in a row
Application.Shutdown ();
// but still report a failure and name the test that didn't shut down. Note
// that the test that didn't shutdown won't be the one currently running it will
// be the last one
throw new Exception (
"A test did not call shutdown correctly. Test stack trace was:" + LastInitFakeDriver
);
}
driver.Init ();
LastInitFakeDriver = Environment.StackTrace;
return driver;
}
/// <summary>
/// Tests that each point in the screen space maps to a rectangle of (float) graph space and that each corner of
/// that rectangle of graph space maps back to the same row/col of the graph that was fed in
/// </summary>
[Fact]
public void TestReversing_ScreenToGraphSpace ()
{
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.Bounds = new Rectangle (0, 0, 50, 30);
// How much graph space each cell of the console depicts
gv.CellSize = new PointF (0.1f, 0.25f);
gv.AxisX.Increment = 1;
gv.AxisX.ShowLabelsEvery = 1;
gv.AxisY.Increment = 1;
gv.AxisY.ShowLabelsEvery = 1;
// Start the graph at 80
gv.ScrollOffset = new PointF (0, 80);
for (var x = 0; x < gv.Bounds.Width; x++)
{
for (var y = 0; y < gv.Bounds.Height; y++)
{
RectangleF graphSpace = gv.ScreenToGraphSpace (x, y);
// See
// https://en.wikipedia.org/wiki/Machine_epsilon
var epsilon = 0.0001f;
Point p = gv.GraphSpaceToScreen (
new PointF (
graphSpace.Left + epsilon,
graphSpace.Top + epsilon
)
);
Assert.Equal (x, p.X);
Assert.Equal (y, p.Y);
p = gv.GraphSpaceToScreen (
new PointF (
graphSpace.Right - epsilon,
graphSpace.Top + epsilon
)
);
Assert.Equal (x, p.X);
Assert.Equal (y, p.Y);
p = gv.GraphSpaceToScreen (
new PointF (
graphSpace.Left + epsilon,
graphSpace.Bottom - epsilon
)
);
Assert.Equal (x, p.X);
Assert.Equal (y, p.Y);
p = gv.GraphSpaceToScreen (
new PointF (
graphSpace.Right - epsilon,
graphSpace.Bottom - epsilon
)
);
Assert.Equal (x, p.X);
Assert.Equal (y, p.Y);
}
}
}
#region Screen to Graph Tests
[Fact]
public void ScreenToGraphSpace_DefaultCellSize ()
{
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.Bounds = new Rectangle (0, 0, 20, 10);
// origin should be bottom left
RectangleF botLeft = gv.ScreenToGraphSpace (0, 9);
Assert.Equal (0, botLeft.X);
Assert.Equal (0, botLeft.Y);
Assert.Equal (1, botLeft.Width);
Assert.Equal (1, botLeft.Height);
// up 2 rows of the console and along 1 col
RectangleF up2along1 = gv.ScreenToGraphSpace (1, 7);
Assert.Equal (1, up2along1.X);
Assert.Equal (2, up2along1.Y);
}
[Fact]
public void ScreenToGraphSpace_DefaultCellSize_WithMargin ()
{
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.Bounds = new Rectangle (0, 0, 20, 10);
// origin should be bottom left
RectangleF botLeft = gv.ScreenToGraphSpace (0, 9);
Assert.Equal (0, botLeft.X);
Assert.Equal (0, botLeft.Y);
Assert.Equal (1, botLeft.Width);
Assert.Equal (1, botLeft.Height);
gv.MarginLeft = 1;
botLeft = gv.ScreenToGraphSpace (0, 9);
// Origin should be at 1,9 now to leave a margin of 1
// so screen position 0,9 would be data space -1,0
Assert.Equal (-1, botLeft.X);
Assert.Equal (0, botLeft.Y);
Assert.Equal (1, botLeft.Width);
Assert.Equal (1, botLeft.Height);
gv.MarginLeft = 1;
gv.MarginBottom = 1;
botLeft = gv.ScreenToGraphSpace (0, 9);
// Origin should be at 1,0 (to leave a margin of 1 in both sides)
// so screen position 0,9 would be data space -1,-1
Assert.Equal (-1, botLeft.X);
Assert.Equal (-1, botLeft.Y);
Assert.Equal (1, botLeft.Width);
Assert.Equal (1, botLeft.Height);
}
[Fact]
public void ScreenToGraphSpace_CustomCellSize ()
{
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.Bounds = new Rectangle (0, 0, 20, 10);
// Each cell of screen measures 5 units in graph data model vertically and 1/4 horizontally
gv.CellSize = new PointF (0.25f, 5);
// origin should be bottom left
// (note that y=10 is actually overspilling the control, the last row is 9)
RectangleF botLeft = gv.ScreenToGraphSpace (0, 9);
Assert.Equal (0, botLeft.X);
Assert.Equal (0, botLeft.Y);
Assert.Equal (0.25f, botLeft.Width);
Assert.Equal (5, botLeft.Height);
// up 2 rows of the console and along 1 col
RectangleF up2along1 = gv.ScreenToGraphSpace (1, 7);
Assert.Equal (0.25f, up2along1.X);
Assert.Equal (10, up2along1.Y);
Assert.Equal (0.25f, botLeft.Width);
Assert.Equal (5, botLeft.Height);
}
#endregion
#region Graph to Screen Tests
[Fact]
public void GraphSpaceToScreen_DefaultCellSize ()
{
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.Bounds = new Rectangle (0, 0, 20, 10);
// origin should be bottom left
Point botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
Assert.Equal (0, botLeft.X);
Assert.Equal (9, botLeft.Y); // row 9 of the view is the bottom left
// along 2 and up 1 in graph space
Point along2up1 = gv.GraphSpaceToScreen (new PointF (2, 1));
Assert.Equal (2, along2up1.X);
Assert.Equal (8, along2up1.Y);
}
[Fact]
public void GraphSpaceToScreen_DefaultCellSize_WithMargin ()
{
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.Bounds = new Rectangle (0, 0, 20, 10);
// origin should be bottom left
Point botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
Assert.Equal (0, botLeft.X);
Assert.Equal (9, botLeft.Y); // row 9 of the view is the bottom left
gv.MarginLeft = 1;
// With a margin of 1 the origin should be at x=1 y= 9
botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
Assert.Equal (1, botLeft.X);
Assert.Equal (9, botLeft.Y); // row 9 of the view is the bottom left
gv.MarginLeft = 1;
gv.MarginBottom = 1;
// With a margin of 1 in both directions the origin should be at x=1 y= 9
botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
Assert.Equal (1, botLeft.X);
Assert.Equal (8, botLeft.Y); // row 8 of the view is the bottom left up 1 cell
}
[Fact]
public void GraphSpaceToScreen_ScrollOffset ()
{
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.Bounds = new Rectangle (0, 0, 20, 10);
//graph is scrolled to present chart space -5 to 5 in both axes
gv.ScrollOffset = new PointF (-5, -5);
// origin should be right in the middle of the control
Point botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
Assert.Equal (5, botLeft.X);
Assert.Equal (4, botLeft.Y);
// along 2 and up 1 in graph space
Point along2up1 = gv.GraphSpaceToScreen (new PointF (2, 1));
Assert.Equal (7, along2up1.X);
Assert.Equal (3, along2up1.Y);
}
[Fact]
public void GraphSpaceToScreen_CustomCellSize ()
{
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.Bounds = new Rectangle (0, 0, 20, 10);
// Each cell of screen is responsible for rendering 5 units in graph data model
// vertically and 1/4 horizontally
gv.CellSize = new PointF (0.25f, 5);
// origin should be bottom left
Point botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
Assert.Equal (0, botLeft.X);
// row 9 of the view is the bottom left (height is 10 so 0,1,2,3..9)
Assert.Equal (9, botLeft.Y);
// along 2 and up 1 in graph space
Point along2up1 = gv.GraphSpaceToScreen (new PointF (2, 1));
Assert.Equal (8, along2up1.X);
Assert.Equal (9, along2up1.Y);
// Y value 4 should be rendered in bottom most row
Assert.Equal (9, gv.GraphSpaceToScreen (new PointF (2, 4)).Y);
// Cell height is 5 so this is the first point of graph space that should
// be rendered in the graph in next row up (row 9)
Assert.Equal (8, gv.GraphSpaceToScreen (new PointF (2, 5)).Y);
// More boundary testing for this cell size
Assert.Equal (8, gv.GraphSpaceToScreen (new PointF (2, 6)).Y);
Assert.Equal (8, gv.GraphSpaceToScreen (new PointF (2, 7)).Y);
Assert.Equal (8, gv.GraphSpaceToScreen (new PointF (2, 8)).Y);
Assert.Equal (8, gv.GraphSpaceToScreen (new PointF (2, 9)).Y);
Assert.Equal (7, gv.GraphSpaceToScreen (new PointF (2, 10)).Y);
Assert.Equal (7, gv.GraphSpaceToScreen (new PointF (2, 11)).Y);
}
[Fact]
public void GraphSpaceToScreen_CustomCellSize_WithScrollOffset ()
{
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.Bounds = new Rectangle (0, 0, 20, 10);
// Each cell of screen is responsible for rendering 5 units in graph data model
// vertically and 1/4 horizontally
gv.CellSize = new PointF (0.25f, 5);
//graph is scrolled to present some negative chart (4 negative cols and 2 negative rows)
gv.ScrollOffset = new PointF (-1, -10);
// origin should be in the lower left (but not right at the bottom)
Point botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
Assert.Equal (4, botLeft.X);
Assert.Equal (7, botLeft.Y);
// along 2 and up 1 in graph space
Point along2up1 = gv.GraphSpaceToScreen (new PointF (2, 1));
Assert.Equal (12, along2up1.X);
Assert.Equal (7, along2up1.Y);
// More boundary testing for this cell size/offset
Assert.Equal (6, gv.GraphSpaceToScreen (new PointF (2, 6)).Y);
Assert.Equal (6, gv.GraphSpaceToScreen (new PointF (2, 7)).Y);
Assert.Equal (6, gv.GraphSpaceToScreen (new PointF (2, 8)).Y);
Assert.Equal (6, gv.GraphSpaceToScreen (new PointF (2, 9)).Y);
Assert.Equal (5, gv.GraphSpaceToScreen (new PointF (2, 10)).Y);
Assert.Equal (5, gv.GraphSpaceToScreen (new PointF (2, 11)).Y);
}
#endregion
}
public class SeriesTests
{
[Fact]
public void Series_GetsPassedCorrectBounds_AllAtOnce ()
{
GraphViewTests.InitFakeDriver ();
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.ColorScheme = new ColorScheme ();
gv.Bounds = new Rectangle (0, 0, 50, 30);
var fullGraphBounds = RectangleF.Empty;
var graphScreenBounds = Rectangle.Empty;
var series = new FakeSeries (
(v, s, g) =>
{
graphScreenBounds = s;
fullGraphBounds = g;
}
);
gv.Series.Add (series);
gv.LayoutSubviews ();
gv.Draw ();
Assert.Equal (new RectangleF (0, 0, 50, 30), fullGraphBounds);
Assert.Equal (new Rectangle (0, 0, 50, 30), graphScreenBounds);
// Now we put a margin in
// Graph should not spill into the margins
gv.MarginBottom = 2;
gv.MarginLeft = 5;
// Even with a margin the graph should be drawn from
// the origin, we just get less visible width/height
gv.LayoutSubviews ();
gv.Draw ();
Assert.Equal (new RectangleF (0, 0, 45, 28), fullGraphBounds);
// The screen space the graph will be rendered into should
// not overspill the margins
Assert.Equal (new Rectangle (5, 0, 45, 28), graphScreenBounds);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
/// <summary>
/// Tests that the bounds passed to the ISeries for drawing into are correct even when the
/// <see cref="GraphView.CellSize"/> results in multiple units of graph space being condensed into each cell of console
/// </summary>
[Fact]
public void Series_GetsPassedCorrectBounds_AllAtOnce_LargeCellSize ()
{
GraphViewTests.InitFakeDriver ();
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.ColorScheme = new ColorScheme ();
gv.Bounds = new Rectangle (0, 0, 50, 30);
// the larger the cell size the more condensed (smaller) the graph space is
gv.CellSize = new PointF (2, 5);
var fullGraphBounds = RectangleF.Empty;
var graphScreenBounds = Rectangle.Empty;
var series = new FakeSeries (
(v, s, g) =>
{
graphScreenBounds = s;
fullGraphBounds = g;
}
);
gv.Series.Add (series);
gv.LayoutSubviews ();
gv.Draw ();
// Since each cell of the console is 2x5 of graph space the graph
// bounds to be rendered are larger
Assert.Equal (new RectangleF (0, 0, 100, 150), fullGraphBounds);
Assert.Equal (new Rectangle (0, 0, 50, 30), graphScreenBounds);
// Graph should not spill into the margins
gv.MarginBottom = 2;
gv.MarginLeft = 5;
// Even with a margin the graph should be drawn from
// the origin, we just get less visible width/height
gv.LayoutSubviews ();
gv.Draw ();
Assert.Equal (new RectangleF (0, 0, 90, 140), fullGraphBounds);
// The screen space the graph will be rendered into should
// not overspill the margins
Assert.Equal (new Rectangle (5, 0, 45, 28), graphScreenBounds);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
private class FakeSeries : ISeries
{
private readonly Action<GraphView, Rectangle, RectangleF> _drawSeries;
public FakeSeries (
Action<GraphView, Rectangle, RectangleF> drawSeries
)
{
_drawSeries = drawSeries;
}
public void DrawSeries (GraphView graph, Rectangle bounds, RectangleF graphBounds) { _drawSeries (graph, bounds, graphBounds); }
}
}
public class MultiBarSeriesTests
{
private readonly ITestOutputHelper _output;
public MultiBarSeriesTests (ITestOutputHelper output) { _output = output; }
[Fact]
public void MultiBarSeries_BarSpacing ()
{
// Creates clusters of 5 adjacent bars with 2 spaces between clusters
var series = new MultiBarSeries (5, 7, 1);
Assert.Equal (5, series.SubSeries.Count);
Assert.Equal (0, series.SubSeries.ElementAt (0).Offset);
Assert.Equal (1, series.SubSeries.ElementAt (1).Offset);
Assert.Equal (2, series.SubSeries.ElementAt (2).Offset);
Assert.Equal (3, series.SubSeries.ElementAt (3).Offset);
Assert.Equal (4, series.SubSeries.ElementAt (4).Offset);
}
[Fact]
public void MultiBarSeriesAddValues_WrongNumber ()
{
// user asks for 3 bars per category
var series = new MultiBarSeries (3, 7, 1);
var ex = Assert.Throws<ArgumentException> (() => series.AddBars ("Cars", (Rune)'#', 1));
Assert.Equal (
"Number of values must match the number of bars per category (Parameter 'values')",
ex.Message
);
}
[Fact]
public void MultiBarSeriesColors_RightNumber ()
{
Attribute [] colors =
{
new (Color.Green, Color.Black), new (Color.Green, Color.White), new (Color.BrightYellow, Color.White)
};
// user passes 3 colors and asks for 3 bars
var series = new MultiBarSeries (3, 7, 1, colors);
Assert.Equal (series.SubSeries.ElementAt (0).OverrideBarColor, colors [0]);
Assert.Equal (series.SubSeries.ElementAt (1).OverrideBarColor, colors [1]);
Assert.Equal (series.SubSeries.ElementAt (2).OverrideBarColor, colors [2]);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void MultiBarSeriesColors_WrongNumber ()
{
Attribute [] colors = { new (Color.Green, Color.Black) };
// user passes 1 color only but asks for 5 bars
var ex = Assert.Throws<ArgumentException> (() => new MultiBarSeries (5, 7, 1, colors));
Assert.Equal (
"Number of colors must match the number of bars (Parameter 'numberOfBarsPerCategory')",
ex.Message
);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TestRendering_MultibarSeries ()
{
GraphViewTests.InitFakeDriver ();
var gv = new GraphView ();
gv.ColorScheme = new ColorScheme ();
// y axis goes from 0.1 to 1 across 10 console rows
// x axis goes from 0 to 20 across 20 console columns
gv.Bounds = new Rectangle (0, 0, 20, 10);
gv.CellSize = new PointF (1f, 0.1f);
gv.MarginBottom = 1;
gv.MarginLeft = 1;
var multibarSeries = new MultiBarSeries (2, 4, 1);
//nudge them left to avoid float rounding errors at the boundaries of cells
foreach (BarSeries sub in multibarSeries.SubSeries)
{
sub.Offset -= 0.001f;
}
gv.Series.Add (multibarSeries);
FakeHAxis fakeXAxis;
// don't show axis labels that means any labels
// that appear are explicitly from the bars
gv.AxisX = fakeXAxis = new FakeHAxis { Increment = 0 };
gv.AxisY = new FakeVAxis { Increment = 0 };
gv.LayoutSubviews ();
gv.Draw ();
// Since bar series has no bars yet no labels should be displayed
Assert.Empty (fakeXAxis.LabelPoints);
multibarSeries.AddBars ("hey", (Rune)'M', 0.5001f, 0.5001f);
fakeXAxis.LabelPoints.Clear ();
gv.LayoutSubviews ();
gv.Draw ();
Assert.Equal (4, fakeXAxis.LabelPoints.Single ());
multibarSeries.AddBars ("there", (Rune)'M', 0.24999f, 0.74999f);
multibarSeries.AddBars ("bob", (Rune)'M', 1, 2);
fakeXAxis.LabelPoints.Clear ();
gv.LayoutSubviews ();
gv.Draw ();
Assert.Equal (3, fakeXAxis.LabelPoints.Count);
Assert.Equal (4, fakeXAxis.LabelPoints [0]);
Assert.Equal (8, fakeXAxis.LabelPoints [1]);
Assert.Equal (12, fakeXAxis.LabelPoints [2]);
var looksLike =
@"
│ MM
│ M MM
│ M MM
│ MM M MM
│ MM M MM
│ MM M MM
│ MM MM MM
│ MM MM MM
┼──┬M──┬M──┬M──────
heytherebob ";
TestHelpers.AssertDriverContentsAre (looksLike, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
}
public class BarSeriesTests
{
[Fact]
public void TestOneLongOneShortHorizontalBars_WithOffset ()
{
GraphView graph = GetGraph (out FakeBarSeries barSeries, out FakeHAxis axisX, out FakeVAxis axisY);
graph.Draw ();
// no bars
Assert.Empty (barSeries.BarScreenStarts);
Assert.Empty (axisX.LabelPoints);
Assert.Empty (axisY.LabelPoints);
// 0.1 units of graph y fit every screen row
// so 1 unit of graph y space is 10 screen rows
graph.CellSize = new PointF (0.5f, 0.1f);
// Start bar 3 screen units up (y = height-3)
barSeries.Offset = 0.25f;
// 1 bar every 3 rows of screen
barSeries.BarEvery = 0.3f;
barSeries.Orientation = Orientation.Horizontal;
// 1 bar that is very wide (100 graph units horizontally = screen pos 50 but bounded by screen)
barSeries.Bars.Add (
new BarSeriesBar ("hi1", new GraphCellToRender ((Rune)'.'), 100)
);
// 1 bar that is shorter
barSeries.Bars.Add (
new BarSeriesBar ("hi2", new GraphCellToRender ((Rune)'.'), 5)
);
// redraw graph
graph.Draw ();
// since bars are horizontal all have the same X start cordinates
Assert.Equal (0, barSeries.BarScreenStarts [0].X);
Assert.Equal (0, barSeries.BarScreenStarts [1].X);
// bar goes all the way to the end so bumps up against right screen boundary
// width of graph is 20
Assert.Equal (19, barSeries.BarScreenEnds [0].X);
// shorter bar is 5 graph units wide which is 10 screen units
Assert.Equal (10, barSeries.BarScreenEnds [1].X);
// first bar should be offset 6 screen units (0.25f + 0.3f graph units)
// since height of control is 10 then first bar should be at screen row 4 (10-6)
Assert.Equal (4, barSeries.BarScreenStarts [0].Y);
// second bar should be offset 9 screen units (0.25f + 0.6f graph units)
// since height of control is 10 then second bar should be at screen row 1 (10-9)
Assert.Equal (1, barSeries.BarScreenStarts [1].Y);
// both bars should have labels but on the y axis
Assert.Equal (2, axisY.LabelPoints.Count);
Assert.Empty (axisX.LabelPoints);
// labels should align with the bars (same screen y axis point)
Assert.Contains (4, axisY.LabelPoints);
Assert.Contains (1, axisY.LabelPoints);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TestTwoTallBars_WithOffset ()
{
GraphView graph = GetGraph (out FakeBarSeries barSeries, out FakeHAxis axisX, out FakeVAxis axisY);
graph.Draw ();
// no bars
Assert.Empty (barSeries.BarScreenStarts);
Assert.Empty (axisX.LabelPoints);
Assert.Empty (axisY.LabelPoints);
// 0.5 units of graph fit every screen cell
// so 1 unit of graph space is 2 screen columns
graph.CellSize = new PointF (0.5f, 0.1f);
// Start bar 1 screen unit along
barSeries.Offset = 0.5f;
barSeries.BarEvery = 1f;
barSeries.Bars.Add (
new BarSeriesBar ("hi1", new GraphCellToRender ((Rune)'.'), 100)
);
barSeries.Bars.Add (
new BarSeriesBar ("hi2", new GraphCellToRender ((Rune)'.'), 100)
);
barSeries.Orientation = Orientation.Vertical;
// redraw graph
graph.Draw ();
// bar should be drawn at BarEvery 1f + offset 0.5f = 3 screen units
Assert.Equal (3, barSeries.BarScreenStarts [0].X);
Assert.Equal (3, barSeries.BarScreenEnds [0].X);
// second bar should be BarEveryx2 = 2f + offset 0.5f = 5 screen units
Assert.Equal (5, barSeries.BarScreenStarts [1].X);
Assert.Equal (5, barSeries.BarScreenEnds [1].X);
// both bars should have labels
Assert.Equal (2, axisX.LabelPoints.Count);
Assert.Contains (3, axisX.LabelPoints);
Assert.Contains (5, axisX.LabelPoints);
// bars are very tall but should not draw up off top of screen
Assert.Equal (9, barSeries.BarScreenStarts [0].Y);
Assert.Equal (0, barSeries.BarScreenEnds [0].Y);
Assert.Equal (9, barSeries.BarScreenStarts [1].Y);
Assert.Equal (0, barSeries.BarScreenEnds [1].Y);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TestZeroHeightBar_WithName ()
{
GraphView graph = GetGraph (out FakeBarSeries barSeries, out FakeHAxis axisX, out FakeVAxis axisY);
graph.Draw ();
// no bars
Assert.Empty (barSeries.BarScreenStarts);
Assert.Empty (axisX.LabelPoints);
Assert.Empty (axisY.LabelPoints);
// bar of height 0
barSeries.Bars.Add (new BarSeriesBar ("hi", new GraphCellToRender ((Rune)'.'), 0));
barSeries.Orientation = Orientation.Vertical;
// redraw graph
graph.Draw ();
// bar should not be drawn
Assert.Empty (barSeries.BarScreenStarts);
Assert.NotEmpty (axisX.LabelPoints);
Assert.Empty (axisY.LabelPoints);
// but bar name should be
// Screen position x=2 because bars are drawn every 1f of
// graph space and CellSize.X is 0.5f
Assert.Contains (2, axisX.LabelPoints);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
private GraphView GetGraph (out FakeBarSeries series, out FakeHAxis axisX, out FakeVAxis axisY)
{
GraphViewTests.InitFakeDriver ();
var gv = new GraphView ();
gv.BeginInit ();
gv.EndInit ();
gv.ColorScheme = new ColorScheme ();
// y axis goes from 0.1 to 1 across 10 console rows
// x axis goes from 0 to 10 across 20 console columns
gv.Bounds = new Rectangle (0, 0, 20, 10);
gv.CellSize = new PointF (0.5f, 0.1f);
gv.Series.Add (series = new FakeBarSeries ());
// don't show axis labels that means any labels
// that appaer are explicitly from the bars
gv.AxisX = axisX = new FakeHAxis { Increment = 0 };
gv.AxisY = axisY = new FakeVAxis { Increment = 0 };
return gv;
}
private class FakeBarSeries : BarSeries
{
public List<Point> BarScreenEnds { get; } = new ();
public List<Point> BarScreenStarts { get; } = new ();
public GraphCellToRender FinalColor { get; private set; }
protected override GraphCellToRender AdjustColor (GraphCellToRender graphCellToRender) { return FinalColor = base.AdjustColor (graphCellToRender); }
protected override void DrawBarLine (GraphView graph, Point start, Point end, BarSeriesBar beingDrawn)
{
base.DrawBarLine (graph, start, end, beingDrawn);
BarScreenStarts.Add (start);
BarScreenEnds.Add (end);
}
}
}
public class AxisTests
{
private GraphView GetGraph (out FakeHAxis axis) { return GetGraph (out axis, out _); }
private GraphView GetGraph (out FakeVAxis axis) { return GetGraph (out _, out axis); }
private GraphView GetGraph (out FakeHAxis axisX, out FakeVAxis axisY)
{
GraphViewTests.InitFakeDriver ();
var gv = new GraphView ();
gv.ColorScheme = new ColorScheme ();
gv.Bounds = new Rectangle (0, 0, 50, 30);
// graph can't be completely empty or it won't draw
gv.Series.Add (new ScatterSeries ());
axisX = new FakeHAxis ();
axisY = new FakeVAxis ();
gv.AxisX = axisX;
gv.AxisY = axisY;
return gv;
}
#region HorizontalAxis Tests
/// <summary>Tests that the horizontal axis is computed correctly and does not over spill it's bounds</summary>
[Fact]
public void TestHAxisLocation_NoMargin ()
{
GraphView gv = GetGraph (out FakeHAxis axis);
gv.LayoutSubviews ();
gv.Draw ();
Assert.DoesNotContain (new Point (-1, 29), axis.DrawAxisLinePoints);
Assert.Contains (new Point (0, 29), axis.DrawAxisLinePoints);
Assert.Contains (new Point (1, 29), axis.DrawAxisLinePoints);
Assert.Contains (new Point (48, 29), axis.DrawAxisLinePoints);
Assert.Contains (new Point (49, 29), axis.DrawAxisLinePoints);
Assert.DoesNotContain (new Point (50, 29), axis.DrawAxisLinePoints);
Assert.InRange (axis.LabelPoints.Max (), 0, 49);
Assert.InRange (axis.LabelPoints.Min (), 0, 49);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TestHAxisLocation_MarginBottom ()
{
GraphView gv = GetGraph (out FakeHAxis axis);
gv.MarginBottom = 10;
gv.LayoutSubviews ();
gv.Draw ();
Assert.DoesNotContain (new Point (-1, 19), axis.DrawAxisLinePoints);
Assert.Contains (new Point (0, 19), axis.DrawAxisLinePoints);
Assert.Contains (new Point (1, 19), axis.DrawAxisLinePoints);
Assert.Contains (new Point (48, 19), axis.DrawAxisLinePoints);
Assert.Contains (new Point (49, 19), axis.DrawAxisLinePoints);
Assert.DoesNotContain (new Point (50, 19), axis.DrawAxisLinePoints);
Assert.InRange (axis.LabelPoints.Max (), 0, 49);
Assert.InRange (axis.LabelPoints.Min (), 0, 49);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TestHAxisLocation_MarginLeft ()
{
GraphView gv = GetGraph (out FakeHAxis axis);
gv.MarginLeft = 5;
gv.LayoutSubviews ();
gv.Draw ();
Assert.DoesNotContain (new Point (4, 29), axis.DrawAxisLinePoints);
Assert.Contains (new Point (5, 29), axis.DrawAxisLinePoints);
Assert.Contains (new Point (6, 29), axis.DrawAxisLinePoints);
Assert.Contains (new Point (48, 29), axis.DrawAxisLinePoints);
Assert.Contains (new Point (49, 29), axis.DrawAxisLinePoints);
Assert.DoesNotContain (new Point (50, 29), axis.DrawAxisLinePoints);
// Axis lables should not be drawn in the margin
Assert.InRange (axis.LabelPoints.Max (), 5, 49);
Assert.InRange (axis.LabelPoints.Min (), 5, 49);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
#endregion
#region VerticalAxisTests
/// <summary>Tests that the horizontal axis is computed correctly and does not over spill it's bounds</summary>
[Fact]
public void TestVAxisLocation_NoMargin ()
{
GraphView gv = GetGraph (out FakeVAxis axis);
gv.LayoutSubviews ();
gv.Draw ();
Assert.DoesNotContain (new Point (0, -1), axis.DrawAxisLinePoints);
Assert.Contains (new Point (0, 1), axis.DrawAxisLinePoints);
Assert.Contains (new Point (0, 2), axis.DrawAxisLinePoints);
Assert.Contains (new Point (0, 28), axis.DrawAxisLinePoints);
Assert.Contains (new Point (0, 29), axis.DrawAxisLinePoints);
Assert.DoesNotContain (new Point (0, 30), axis.DrawAxisLinePoints);
Assert.InRange (axis.LabelPoints.Max (), 0, 29);
Assert.InRange (axis.LabelPoints.Min (), 0, 29);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TestVAxisLocation_MarginBottom ()
{
GraphView gv = GetGraph (out FakeVAxis axis);
gv.MarginBottom = 10;
gv.LayoutSubviews ();
gv.Draw ();
Assert.DoesNotContain (new Point (0, -1), axis.DrawAxisLinePoints);
Assert.Contains (new Point (0, 1), axis.DrawAxisLinePoints);
Assert.Contains (new Point (0, 2), axis.DrawAxisLinePoints);
Assert.Contains (new Point (0, 18), axis.DrawAxisLinePoints);
Assert.Contains (new Point (0, 19), axis.DrawAxisLinePoints);
Assert.DoesNotContain (new Point (0, 20), axis.DrawAxisLinePoints);
// Labels should not be drawn into the axis
Assert.InRange (axis.LabelPoints.Max (), 0, 19);
Assert.InRange (axis.LabelPoints.Min (), 0, 19);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TestVAxisLocation_MarginLeft ()
{
GraphView gv = GetGraph (out FakeVAxis axis);
gv.MarginLeft = 5;
gv.LayoutSubviews ();
gv.Draw ();
Assert.DoesNotContain (new Point (5, -1), axis.DrawAxisLinePoints);
Assert.Contains (new Point (5, 1), axis.DrawAxisLinePoints);
Assert.Contains (new Point (5, 2), axis.DrawAxisLinePoints);
Assert.Contains (new Point (5, 28), axis.DrawAxisLinePoints);
Assert.Contains (new Point (5, 29), axis.DrawAxisLinePoints);
Assert.DoesNotContain (new Point (5, 30), axis.DrawAxisLinePoints);
Assert.InRange (axis.LabelPoints.Max (), 0, 29);
Assert.InRange (axis.LabelPoints.Min (), 0, 29);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
#endregion
}
public class TextAnnotationTests
{
private readonly ITestOutputHelper _output;
public TextAnnotationTests (ITestOutputHelper output) { _output = output; }
[Theory]
[InlineData (null)]
[InlineData (" ")]
[InlineData ("\t\t")]
public void TestTextAnnotation_EmptyText (string whitespace)
{
GraphView gv = GraphViewTests.GetGraph ();
gv.Annotations.Add (
new TextAnnotation { Text = whitespace, GraphPosition = new PointF (4, 2) }
);
// add a point a bit further along the graph so if the whitespace were rendered
// the test would pick it up (AssertDriverContentsAre ignores trailing whitespace on lines)
var points = new ScatterSeries ();
points.Points.Add (new PointF (7, 2));
gv.Series.Add (points);
gv.LayoutSubviews ();
gv.Draw ();
var expected =
@$"
┤ {
CM.Glyphs.Dot
}
0┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TestTextAnnotation_GraphUnits ()
{
GraphView gv = GraphViewTests.GetGraph ();
gv.Annotations.Add (
new TextAnnotation { Text = "hey!", GraphPosition = new PointF (2, 2) }
);
gv.LayoutSubviews ();
gv.Draw ();
var expected =
@"
┤ hey!
0┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// user scrolls up one unit of graph space
gv.ScrollOffset = new PointF (0, 1f);
gv.Draw ();
// we expect the text annotation to go down one line since
// the scroll offset means that that point of graph space is
// lower down in the view. Note the 1 on the axis too, our viewport
// (excluding margins) now shows y of 1 to 4 (previously 0 to 5)
expected =
@"
┤ hey!
1┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TestTextAnnotation_LongText ()
{
GraphView gv = GraphViewTests.GetGraph ();
gv.Annotations.Add (
new TextAnnotation
{
Text = "hey there partner hows it going boy its great", GraphPosition = new PointF (2, 2)
}
);
gv.LayoutSubviews ();
gv.Draw ();
// long text should get truncated
// margin takes up 1 units
// the GraphPosition of the anntation is 2
// Leaving 7 characters of the annotation renderable (including space)
var expected =
@"
┤ hey the
0┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TestTextAnnotation_Offscreen ()
{
GraphView gv = GraphViewTests.GetGraph ();
gv.Annotations.Add (
new TextAnnotation
{
Text = "hey there partner hows it going boy its great", GraphPosition = new PointF (9, 2)
}
);
gv.LayoutSubviews ();
gv.Draw ();
// Text is off the screen (graph x axis runs to 8 not 9)
var expected =
@"
0┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void TestTextAnnotation_ScreenUnits ()
{
GraphView gv = GraphViewTests.GetGraph ();
gv.Annotations.Add (
new TextAnnotation { Text = "hey!", ScreenPosition = new Point (3, 1) }
);
gv.LayoutSubviews ();
gv.Draw ();
var expected =
@"
┤ hey!
0┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// user scrolls up one unit of graph space
gv.ScrollOffset = new PointF (0, 1f);
gv.Draw ();
// we expect no change in the location of the annotation (only the axis label changes)
// this is because screen units are constant and do not change as the viewport into
// graph space scrolls to different areas of the graph
expected =
@"
┤ hey!
1┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// user scrolls up one unit of graph space
gv.ScrollOffset = new PointF (0, 1f);
gv.Draw ();
// we expect no change in the location of the annotation (only the axis label changes)
// this is because screen units are constant and do not change as the viewport into
// graph space scrolls to different areas of the graph
expected =
@"
┤ hey!
1┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
}
public class LegendTests
{
private readonly ITestOutputHelper _output;
public LegendTests (ITestOutputHelper output) { _output = output; }
[Fact]
public void Constructors_Defaults ()
{
var legend = new LegendAnnotation ();
Assert.Equal (Rectangle.Empty, legend.Bounds);
Assert.Equal (Rectangle.Empty, legend.Frame);
Assert.Equal (LineStyle.Single, legend.BorderStyle);
Assert.False (legend.BeforeSeries);
var bounds = new Rectangle (1, 2, 10, 3);
legend = new LegendAnnotation (bounds);
Assert.Equal (new Rectangle (0, 0, 8, 1), legend.Bounds);
Assert.Equal (bounds, legend.Frame);
Assert.Equal (LineStyle.Single, legend.BorderStyle);
Assert.False (legend.BeforeSeries);
legend.BorderStyle = LineStyle.None;
Assert.Equal (new Rectangle (0, 0, 10, 3), legend.Bounds);
Assert.Equal (bounds, legend.Frame);
}
[Fact]
public void LegendNormalUsage_WithBorder ()
{
GraphView gv = GraphViewTests.GetGraph ();
var legend = new LegendAnnotation (new Rectangle (2, 0, 5, 3));
legend.AddEntry (new GraphCellToRender ((Rune)'A'), "Ant");
legend.AddEntry (new GraphCellToRender ((Rune)'B'), "Bat");
gv.Annotations.Add (legend);
gv.Draw ();
var expected =
@"
│┌───┐
┤│AAn│
┤└───┘
0┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void LegendNormalUsage_WithoutBorder ()
{
GraphView gv = GraphViewTests.GetGraph ();
var legend = new LegendAnnotation (new Rectangle (2, 0, 5, 3));
legend.AddEntry (new GraphCellToRender ((Rune)'A'), "Ant");
legend.AddEntry (new GraphCellToRender ((Rune)'B'), "?"); // this will exercise pad
legend.AddEntry (new GraphCellToRender ((Rune)'C'), "Cat");
legend.AddEntry (new GraphCellToRender ((Rune)'H'), "Hattter"); // not enough space for this oen
legend.BorderStyle = LineStyle.None;
gv.Annotations.Add (legend);
gv.Draw ();
var expected =
@"
│AAnt
┤B?
┤CCat
0┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
}
public class PathAnnotationTests
{
private readonly ITestOutputHelper _output;
public PathAnnotationTests (ITestOutputHelper output) { _output = output; }
[Fact]
public void MarginBottom_BiggerThanHeight_ExpectBlankGraph ()
{
GraphView gv = GraphViewTests.GetGraph ();
gv.Height = 10;
gv.MarginBottom = 20;
gv.Series.Add (
new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } }
);
gv.LayoutSubviews ();
gv.Draw ();
var expected =
@"
";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void MarginLeft_BiggerThanWidth_ExpectBlankGraph ()
{
GraphView gv = GraphViewTests.GetGraph ();
gv.Width = 10;
gv.MarginLeft = 20;
gv.Series.Add (
new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } }
);
gv.LayoutSubviews ();
gv.Draw ();
var expected =
@"
";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void PathAnnotation_Box ()
{
GraphView gv = GraphViewTests.GetGraph ();
var path = new PathAnnotation ();
path.Points.Add (new PointF (1, 1));
path.Points.Add (new PointF (1, 3));
path.Points.Add (new PointF (6, 3));
path.Points.Add (new PointF (6, 1));
// list the starting point again so that it draws a complete square
// (otherwise it will miss out the last line along the bottom)
path.Points.Add (new PointF (1, 1));
gv.Annotations.Add (path);
gv.LayoutSubviews ();
gv.Draw ();
var expected =
@"
│......
┤. .
┤......
0┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void PathAnnotation_Diamond ()
{
GraphView gv = GraphViewTests.GetGraph ();
var path = new PathAnnotation ();
path.Points.Add (new PointF (1, 2));
path.Points.Add (new PointF (3, 3));
path.Points.Add (new PointF (6, 2));
path.Points.Add (new PointF (3, 1));
// list the starting point again to close the shape
path.Points.Add (new PointF (1, 2));
gv.Annotations.Add (path);
gv.LayoutSubviews ();
gv.Draw ();
var expected =
@"
│ ..
┤.. ..
┤ ...
0┼┬┬┬┬┬┬┬┬
0 5";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Theory]
[InlineData (true)]
[InlineData (false)]
public void ViewChangeText_RendersCorrectly (bool useFill)
{
var driver = new FakeDriver ();
Application.Init (driver);
driver.Init ();
// create a wide window
var mount = new View { Width = 100, Height = 100 };
try
{
// Create a view with a short text
var view = new View { Text = "ff", Width = 2, Height = 1 };
// Specify that the label should be very wide
if (useFill)
{
view.Width = Dim.Fill ();
}
else
{
view.Width = 100;
}
//put label into view
mount.Add (view);
//putting mount into Toplevel since changing size
//also change AutoSize to false
Application.Top.Add (mount);
Application.Begin (Application.Top);
// render view
view.ColorScheme = new ColorScheme ();
Assert.Equal (1, view.Height);
mount.Draw ();
// should have the initial text
TestHelpers.AssertDriverContentsAre ("ff", null);
// change the text and redraw
view.Text = "ff1234";
mount.Draw ();
// should have the new text rendered
TestHelpers.AssertDriverContentsAre ("ff1234", null);
}
finally
{
Application.Shutdown ();
}
}
[Fact]
public void XAxisLabels_With_MarginLeft ()
{
GraphViewTests.InitFakeDriver ();
var gv = new GraphView { ColorScheme = new ColorScheme (), Bounds = new Rectangle (0, 0, 10, 7) };
gv.CellSize = new PointF (1, 0.5f);
gv.AxisY.Increment = 1;
gv.AxisY.ShowLabelsEvery = 1;
gv.Series.Add (
new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } }
);
// reserve 3 cells of the left for the margin
gv.MarginLeft = 3;
gv.MarginBottom = 1;
gv.LayoutSubviews ();
gv.Draw ();
var expected =
@$"
2┤
1┤{
CM.Glyphs.Dot
}
0┼┬┬┬┬{
CM.Glyphs.Dot
}┬
0 5
";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
public void YAxisLabels_With_MarginBottom ()
{
GraphViewTests.InitFakeDriver ();
var gv = new GraphView { ColorScheme = new ColorScheme (), Bounds = new Rectangle (0, 0, 10, 7) };
gv.CellSize = new PointF (1, 0.5f);
gv.AxisY.Increment = 1;
gv.AxisY.ShowLabelsEvery = 1;
gv.Series.Add (
new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } }
);
// reserve 3 cells of the console for the margin
gv.MarginBottom = 3;
gv.MarginLeft = 1;
gv.LayoutSubviews ();
gv.Draw ();
var expected =
@$"
1┤{
CM.Glyphs.Dot
}
0┼┬┬┬┬{
CM.Glyphs.Dot
}┬┬┬
0 5
";
TestHelpers.AssertDriverContentsAre (expected, _output);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
}
public class AxisIncrementToRenderTests
{
[Fact]
public void AxisIncrementToRenderTests_Constructor ()
{
var render = new AxisIncrementToRender (Orientation.Horizontal, 1, 6.6f);
Assert.Equal (Orientation.Horizontal, render.Orientation);
Assert.Equal (1, render.ScreenLocation);
Assert.Equal (6.6f, render.Value);
}
}