Split UniformGrid code, Start Unit Tests for Array.Fill

This commit is contained in:
mhawker 2018-04-06 20:59:40 -07:00
Родитель 4547db2ab9
Коммит 3bba66d268
5 изменённых файлов: 581 добавлений и 103 удалений

Просмотреть файл

@ -0,0 +1,113 @@
// ******************************************************************
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
[assembly: InternalsVisibleTo("UnitTests")]
namespace Microsoft.Toolkit.Uwp.UI.Controls
{
/// <summary>
/// The UniformGrid control presents information within a Grid with even spacing.
/// </summary>
public partial class UniformGrid : Grid
{
// Provides the next spot in the boolean array with a 'false' value.
#pragma warning disable SA1009 // Closing parenthesis must be followed by a space.
internal static IEnumerable<(int row, int column)> GetFreeSpot(bool[,] array, int firstcolumn, bool reverse)
#pragma warning restore SA1009 // Closing parenthesis must be followed by a space.
{
if (!reverse)
{
for (int r = 0; r < array.GetLength(0); r++)
{
int start = (r == 0 && firstcolumn > 0) ? firstcolumn : 0;
for (int c = start; c < array.GetLength(1); c++)
{
if (!array[r, c])
{
yield return (r, c);
}
}
}
}
else
{
for (int r = 0; r < array.GetLength(0); r++)
{
int start = (r == 0 && firstcolumn > 0) ? firstcolumn : array.GetLength(1) - 1;
for (int c = start; c >= 0; c--)
{
if (!array[r, c])
{
yield return (r, c);
}
}
}
}
}
// Based on the number of visible children,
// returns the dimensions of the
// grid we need to hold all elements.
#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly
internal (int rows, int columns) GetDimensions(ref IEnumerable<FrameworkElement> visible)
#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly
{
// Make copy of our properties as we don't want to modify.
int rows = Rows;
int cols = Columns;
// If a dimension isn't specified, we need to figure out the other one (or both).
if (rows == 0 || cols == 0)
{
// Calculate the size & area of all objects in the grid to know how much space we need.
// TODO: Need to trim size of objects that go out of bounds?
// TODO: Do we need to worry if there aren't enough small items to fill in the gaps?
var count = visible.Sum(item => GetRowSpan(item) * GetColumnSpan(item));
if (rows == 0)
{
if (cols > 0)
{
// TODO: Handle RightToLeft?
var first = Math.Min(FirstColumn, cols - 1); // Bound check
// If we have columns but no rows, calculate rows based on column offset and number of children.
rows = (count + first + (cols - 1)) / cols;
return (rows, cols);
}
// Otherwise, determine square layout if both are zero.
rows = (int)Math.Ceiling(Math.Sqrt(count));
return (rows, rows);
}
else if (cols == 0)
{
// If we have rows and no columns, then calculate columns needed based on rows
cols = (count + (rows - 1)) / rows;
// Now that we know a rough size of our shape, see if the FirstColumn effects that:
var first = Math.Min(FirstColumn, cols - 1); // Bound check
cols = (count + first + (rows - 1)) / rows;
}
}
return (rows, cols);
}
}
}

Просмотреть файл

@ -11,10 +11,8 @@
// ******************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Toolkit.Extensions;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -26,54 +24,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// </summary>
public partial class UniformGrid : Grid
{
private static void Fill(ref bool[,] array, int row, int col, int width, int height)
{
for (int r = row; r < row + width; r++)
{
for (int c = col; c < col + height; c++)
{
if (r < array.GetLength(0) && c < array.GetLength(1))
{
array[r, c] = true;
}
}
}
}
#pragma warning disable SA1009 // Closing parenthesis must be followed by a space.
private static IEnumerable<(int row, int column)> GetFreeSpot(bool[,] array, int firstcolumn, bool reverse)
#pragma warning restore SA1009 // Closing parenthesis must be followed by a space.
{
if (!reverse)
{
for (int r = 0; r < array.GetLength(0); r++)
{
int start = (r == 0 && firstcolumn > 0) ? firstcolumn : 0;
for (int c = start; c < array.GetLength(1); c++)
{
if (!array[r, c])
{
yield return (r, c);
}
}
}
}
else
{
for (int r = 0; r < array.GetLength(0); r++)
{
int start = (r == 0 && firstcolumn > 0) ? firstcolumn : array.GetLength(1) - 1;
for (int c = start; c >= 0; c--)
{
if (!array[r, c])
{
yield return (r, c);
}
}
}
}
}
/// <summary>
/// Measure the controls before layout.
/// </summary>
@ -170,7 +120,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
var colspan = GetColumnSpan(child);
// TODO: Document
// If an element needs to be forced in the 0, 0 position, they should manually set UniformGrid.AutoLayout to False for that element.
// If an element needs to be forced in the 0, 0 position,
// they should manually set UniformGrid.AutoLayout to False for that element.
if (row == 0 && col == 0 && GetAutoLayout(child) == null)
{
SetAutoLayout(child, true);
@ -178,7 +129,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
else
{
SetAutoLayout(child, false);
Fill(ref spots, row, col, rowspan, colspan);
spots.Fill(true, row, col, rowspan, colspan);
}
}
@ -230,55 +181,5 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
return desiredSize;
}
// Based on the number of visible children,
// returns the dimensions of the
// grid we need to hold all elements.
#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly
private (int rows, int columns) GetDimensions(ref IEnumerable<FrameworkElement> visible)
#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly
{
// Make copy of our properties as we don't want to modify.
int rows = Rows;
int cols = Columns;
// If a dimension isn't specified, we need to figure out the other one (or both).
if (rows == 0 || cols == 0)
{
// Calculate the size & area of all objects in the grid to know how much space we need.
// TODO: Need to trim size of objects that go out of bounds?
// TODO: Do we need to worry if there aren't enough small items to fill in the gaps?
var count = visible.Sum(item => GetRowSpan(item) * GetColumnSpan(item));
if (rows == 0)
{
if (cols > 0)
{
// TODO: Handle RightToLeft?
var first = Math.Min(FirstColumn, cols - 1); // Bound check
// If we have columns but no rows, calculate rows based on column offset and number of children.
rows = (count + first + (cols - 1)) / cols;
return (rows, cols);
}
// Otherwise, determine square layout if both are zero.
rows = (int)Math.Ceiling(Math.Sqrt(count));
return (rows, rows);
}
else if (cols == 0)
{
// If we have rows and no columns, then calculate columns needed based on rows
cols = (count + (rows - 1)) / rows;
// Now that we know a rough size of our shape, see if the FirstColumn effects that:
var first = Math.Min(FirstColumn, cols - 1); // Bound check
cols = (count + first + (rows - 1)) / rows;
}
}
return (rows, cols);
}
}
}

Просмотреть файл

@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Toolkit.Extensions
{
/// <summary>
/// Helper extension methods for Arrays.
/// </summary>
public static class ArrayExtensions
{
/// <summary>
/// Fills in values of a multi-dimensional rectangular array to specified value based on the position and size given.
/// Ranges given outside the bounds of the array will fill in as much as possible and ignore elements that should appear outside it.
/// Won't throw bounds exception, just won't do work if ranges out of bounds.
/// </summary>
/// <typeparam name="T">Type of array values.</typeparam>
/// <param name="array">Extended type instance.</param>
/// <param name="value">Value to fill with.</param>
/// <param name="row">Row to start on (inclusive, zero-index).</param>
/// <param name="col">Column to start on (inclusive, zero-index).</param>
/// <param name="width">Positive Width of area to fill.</param>
/// <param name="height">Positive Height of area to fill.</param>
public static void Fill<T>(this T[,] array, T value, int row, int col, int width, int height)
{
for (int r = row; r < row + height; r++)
{
for (int c = col; c < col + width; c++)
{
if (r >= 0 && c >= 0 && r < array.GetLength(0) && c < array.GetLength(1))
{
array[r, c] = value;
}
}
}
}
/// <summary>
/// Retrieve a row as an enumerable from a multi-dimensional rectangular array.
/// </summary>
/// <typeparam name="T">Type of rectangular array.</typeparam>
/// <param name="rectarray">Extended type instance.</param>
/// <param name="row">Row record to retrieve, 0-based index.</param>
/// <returns>Yielded Enumerable of results.</returns>
public static IEnumerable<T> GetRow<T>(this T[,] rectarray, int row)
{
if (row < 0 || row >= rectarray.GetLength(0))
{
throw new ArgumentOutOfRangeException(nameof(row));
}
for (int c = 0; c < rectarray.GetLength(1); c++)
{
yield return rectarray[row, c];
}
}
/// <summary>
/// Retrieve a column from a multi-dimensional rectangular array.
/// </summary>
/// <typeparam name="T">Type of rectangular array.</typeparam>
/// <param name="rectarray">Extended type instance.</param>
/// <param name="column">Column record to retrieve, 0-based index.</param>
/// <returns>Yielded Enumerable of results.</returns>
public static IEnumerable<T> GetColumn<T>(this T[,] rectarray, int column)
{
if (column < 0 || column >= rectarray.GetLength(1))
{
throw new ArgumentOutOfRangeException(nameof(column));
}
for (int r = 0; r < rectarray.GetLength(0); r++)
{
yield return rectarray[r, column];
}
}
/// <summary>
/// Retrieve a column from a multi-dimensional jagged array.
/// Will throw an exception if the column is out of bounds, and return default in places where there are no elements from inner arrays.
/// Note: No equivalent GetRow method, as you can use array[row] to retrieve.
/// </summary>
/// <typeparam name="T">Type of jagged array.</typeparam>
/// <param name="rectarray">Extended type instance.</param>
/// <param name="column">Column record to retrieve, 0-based index.</param>
/// <returns>Yielded Enumerable of column elements for given column and default values for smaller inner arrays.</returns>
public static IEnumerable<T> GetColumn<T>(this T[][] rectarray, int column)
{
if (column < 0 || column >= rectarray.Max(array => array.Length))
{
throw new ArgumentOutOfRangeException(nameof(column));
}
for (int r = 0; r < rectarray.GetLength(0); r++)
{
if (column >= rectarray[r].Length)
{
yield return default(T);
continue;
}
yield return rectarray[r][column];
}
}
/// <summary>
/// Joins the multi-dimensional array together in a string representation.
/// </summary>
/// <typeparam name="T">Type of jagged array.</typeparam>
/// <param name="mdarray">Extended type instance.</param>
/// <returns>String representation of array.</returns>
public static string ToArrayString<T>(this T[][] mdarray)
{
string[] inner = new string[mdarray.GetLength(0)];
for (int r = 0; r < mdarray.GetLength(0); r++)
{
inner[r] = string.Join(",\t", mdarray[r]);
}
return "[[" + string.Join("]," + Environment.NewLine + " [", inner) + "]]";
}
/// <summary>
/// Joins the rectangular-array together in a string representation.
/// </summary>
/// <typeparam name="T">Type of rectangular array.</typeparam>
/// <param name="rectarray">Extended type instance.</param>
/// <returns>String representation of array.</returns>
public static string ToArrayString<T>(this T[,] rectarray)
{
string[] inner = new string[rectarray.GetLength(0)];
for (int r = 0; r < rectarray.GetLength(0); r++)
{
inner[r] = string.Join(",\t", rectarray.GetRow(r));
}
return "[[" + string.Join("]," + Environment.NewLine + " [", inner) + "]]";
}
}
}

Просмотреть файл

@ -0,0 +1,319 @@

using Microsoft.Toolkit.Extensions;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using System;
using System.Diagnostics;
using System.Linq;
using Assert = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.Assert;
namespace UnitTests.Extensions
{
[TestClass]
public class Test_ArrayExtensions
{
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayMid()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 1, 1, 3, 2);
var expected = new bool[,]
{
{ false, false, false, false, false },
{ false, true, true, true, false },
{ false, true, true, true, false },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayTwice()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 0, 0, 1, 2);
test.Fill(true, 1, 3, 2, 2);
var expected = new bool[,]
{
{ true, false, false, false, false },
{ true, false, false, true, true },
{ false, false, false, true, true },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayNegativeSize()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 3, 4, -3, -2);
// TODO: We may want to think about this pattern in the future:
/*var expected = new bool[,]
{
{ false, false, false, false, false },
{ false, false, false, false, false },
{ false, false, true, true, true },
{ false, false, true, true, true },
};*/
var expected = new bool[,]
{
{ false, false, false, false, false },
{ false, false, false, false, false },
{ false, false, false, false, false },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayBottomEdgeBoundary()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 1, 2, 2, 4);
var expected = new bool[,]
{
{ false, false, false, false, false },
{ false, false, true, true, false },
{ false, false, true, true, false },
{ false, false, true, true, false },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayTopLeftCornerNegativeBoundary()
{
bool[,] test = new bool[4, 5];
test.Fill(true, -1, -1, 3, 3);
var expected = new bool[,]
{
{ true, true, false, false, false },
{ true, true, false, false, false },
{ false, false, false, false, false },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayBottomRightCornerBoundary()
{
bool[,] test = new bool[5, 4];
test.Fill(true, 3, 2, 3, 3);
var expected = new bool[,]
{
{ false, false, false, false },
{ false, false, false, false },
{ false, false, false, false },
{ false, false, true, true },
{ false, false, true, true },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Jagged_GetColumn()
{
int[][] array =
{
new int[] { 5, 2, 4 },
new int[] { 6, 3 },
new int[] { 7 }
};
var col = array.GetColumn(1).ToArray();
CollectionAssert.AreEquivalent(new int[] { 2, 3, 0 }, col);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Jagged_GetColumn_Exception()
{
int[][] array =
{
new int[] { 5, 2, 4 },
new int[] { 6, 3 },
new int[] { 7 }
};
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
array.GetColumn(-1).ToArray();
});
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
array.GetColumn(3).ToArray();
});
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Rectangular_GetColumn()
{
int[,] array =
{
{ 5, 2, 4 },
{ 6, 3, 9 },
{ 7, -1, 0 }
};
var col = array.GetColumn(1).ToArray();
CollectionAssert.AreEquivalent(new int[] { 2, 3, -1 }, col);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Rectangular_GetColumn_Exception()
{
int[,] array =
{
{ 5, 2, 4 },
{ 6, 3, 0 },
{ 7, 0, 0 }
};
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
array.GetColumn(-1).ToArray();
});
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
array.GetColumn(3).ToArray();
});
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Rectangular_GetRow()
{
int[,] array =
{
{ 5, 2, 4 },
{ 6, 3, 9 },
{ 7, -1, 0 }
};
var col = array.GetRow(1).ToArray();
CollectionAssert.AreEquivalent(new int[] { 6, 3, 9 }, col);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Rectangular_GetRow_Exception()
{
int[,] array =
{
{ 5, 2, 4 },
{ 6, 3, 0 },
{ 7, 0, 0 }
};
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
array.GetRow(-1).ToArray();
});
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
array.GetRow(3).ToArray();
});
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Rectangular_ToString()
{
int[,] array =
{
{ 5, 2, 4 },
{ 6, 3, -1 },
{ 7, 0, 9 }
};
string value = array.ToArrayString();
Debug.WriteLine(value);
Assert.AreEqual("[[5,\t2,\t4]," + Environment.NewLine + " [6,\t3,\t-1]," + Environment.NewLine + " [7,\t0,\t9]]", value);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Jagged_ToString()
{
int[][] array =
{
new int[] { 5, 2 },
new int[] { 6, 3, -1, 2 },
new int[] { 7, 0, 9 }
};
string value = array.ToArrayString();
Debug.WriteLine(value);
Assert.AreEqual("[[5,\t2]," + Environment.NewLine + " [6,\t3,\t-1,\t2]," + Environment.NewLine + " [7,\t0,\t9]]", value);
}
}
}

Просмотреть файл

@ -127,6 +127,7 @@
<Compile Include="Converters\Test_BoolToObjectConverter.cs" />
<Compile Include="Converters\Test_EmptyCollectionToObjectConverter.cs" />
<Compile Include="Converters\Test_EmptyStringToObjectConverter.cs" />
<Compile Include="Extensions\Test_ArrayExtensions.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Helpers\Test_BackgroundTaskHelper.cs" />
<Compile Include="Helpers\Test_ColorHelper.cs" />