Skip to content
This repository was archived by the owner on Jun 24, 2026. It is now read-only.

Commit 24334e1

Browse files
tigCopilot
andcommitted
Add comprehensive unit tests for OutTableViewDataSource and OutTableViewWindow
OutTableViewDataSourceTests (27 tests): - Construction, ColumnNames, indexer bounds checking - AddRow including concurrent thread-safety test - GetOriginalObjectIndex tracking - Filter: empty/null returns self, regex matching, case-insensitive, cross-column, preserves original index, invalid regex throws - Sort: ascending/descending, numeric-aware, preserves index, glyph indicators, invalid index returns self, no mutation of original OutTableViewWindowIntegrationTests (22 tests): - Rendering: headers, data, filter, MinUI - ESC/close behavior - Accept/selection: cursor row, multi-select, single mode, Ctrl+A - Filter interaction: typing filters, Enter accepts from filter - Focus: Table vs Filter targets - Search: positions cursor, regex, no-match stays at top - Streaming: OnDataChanged updates display, OnPipelineComplete - Navigation: Home/End keys Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8278a28 commit 24334e1

2 files changed

Lines changed: 945 additions & 0 deletions

File tree

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Collections.Generic;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Microsoft.PowerShell.OutGridView.Models;
8+
using Xunit;
9+
10+
namespace Microsoft.PowerShell.ConsoleGuiTools.Tests;
11+
12+
/// <summary>
13+
/// Unit tests for <see cref="OutTableViewDataSource" /> covering filtering, sorting,
14+
/// thread-safe row addition, and index tracking.
15+
/// </summary>
16+
public class OutTableViewDataSourceTests
17+
{
18+
#region Helpers
19+
20+
private static List<DataTableColumn> CreateColumns(params string[] names)
21+
{
22+
var columns = new List<DataTableColumn>();
23+
foreach (var name in names)
24+
columns.Add(new DataTableColumn(name, name));
25+
return columns;
26+
}
27+
28+
private static DataTableRow CreateRow(List<DataTableColumn> columns, int originalIndex, params string[] values)
29+
{
30+
var dict = new Dictionary<string, IValue>();
31+
for (int i = 0; i < columns.Count && i < values.Length; i++)
32+
dict[columns[i].ToString()] = new StringValue { DisplayValue = values[i] };
33+
return new DataTableRow(dict, originalIndex);
34+
}
35+
36+
private static OutTableViewDataSource CreateTestDataSource()
37+
{
38+
var columns = CreateColumns("Name", "Id");
39+
var ds = new OutTableViewDataSource(columns);
40+
ds.AddRow(CreateRow(columns, 0, "Charlie", "3"));
41+
ds.AddRow(CreateRow(columns, 1, "Alice", "1"));
42+
ds.AddRow(CreateRow(columns, 2, "Bob", "2"));
43+
return ds;
44+
}
45+
46+
#endregion
47+
48+
#region Construction and Basic Properties
49+
50+
[Fact]
51+
public void Constructor_EmptyDataSource_HasZeroRows()
52+
{
53+
var columns = CreateColumns("Name", "Id");
54+
var ds = new OutTableViewDataSource(columns);
55+
56+
Assert.Equal(0, ds.Rows);
57+
Assert.Equal(2, ds.Columns);
58+
}
59+
60+
[Fact]
61+
public void Constructor_WithRows_HasCorrectRowCount()
62+
{
63+
var columns = CreateColumns("Name");
64+
var rows = new List<DataTableRow>
65+
{
66+
CreateRow(columns, 0, "A"),
67+
CreateRow(columns, 1, "B")
68+
};
69+
var ds = new OutTableViewDataSource(columns, rows);
70+
71+
Assert.Equal(2, ds.Rows);
72+
}
73+
74+
[Fact]
75+
public void ColumnNames_ReturnsLabels()
76+
{
77+
var columns = CreateColumns("Name", "Id", "Status");
78+
var ds = new OutTableViewDataSource(columns);
79+
80+
Assert.Equal(new[] { "Name", "Id", "Status" }, ds.ColumnNames);
81+
}
82+
83+
[Fact]
84+
public void Indexer_ReturnsCorrectCellValue()
85+
{
86+
var ds = CreateTestDataSource();
87+
88+
Assert.Equal("Charlie", ds[0, 0]);
89+
Assert.Equal("3", ds[0, 1]);
90+
Assert.Equal("Alice", ds[1, 0]);
91+
Assert.Equal("1", ds[1, 1]);
92+
}
93+
94+
[Fact]
95+
public void Indexer_OutOfBounds_ReturnsEmptyString()
96+
{
97+
var ds = CreateTestDataSource();
98+
99+
Assert.Equal(string.Empty, ds[-1, 0]);
100+
Assert.Equal(string.Empty, ds[99, 0]);
101+
Assert.Equal(string.Empty, ds[0, -1]);
102+
Assert.Equal(string.Empty, ds[0, 99]);
103+
}
104+
105+
#endregion
106+
107+
#region AddRow
108+
109+
[Fact]
110+
public void AddRow_IncreasesRowCount()
111+
{
112+
var columns = CreateColumns("Name");
113+
var ds = new OutTableViewDataSource(columns);
114+
115+
ds.AddRow(CreateRow(columns, 0, "First"));
116+
Assert.Equal(1, ds.Rows);
117+
118+
ds.AddRow(CreateRow(columns, 1, "Second"));
119+
Assert.Equal(2, ds.Rows);
120+
}
121+
122+
[Fact]
123+
public async Task AddRow_ThreadSafe_ConcurrentAdds()
124+
{
125+
var columns = CreateColumns("Name");
126+
var ds = new OutTableViewDataSource(columns);
127+
const int threadCount = 10;
128+
const int rowsPerThread = 100;
129+
130+
var barrier = new Barrier(threadCount);
131+
var tasks = new Task[threadCount];
132+
133+
for (int t = 0; t < threadCount; t++)
134+
{
135+
int threadId = t;
136+
tasks[t] = Task.Run(() =>
137+
{
138+
barrier.SignalAndWait();
139+
for (int i = 0; i < rowsPerThread; i++)
140+
ds.AddRow(CreateRow(columns, threadId * rowsPerThread + i, $"Row_{threadId}_{i}"));
141+
});
142+
}
143+
144+
await Task.WhenAll(tasks);
145+
Assert.Equal(threadCount * rowsPerThread, ds.Rows);
146+
}
147+
148+
#endregion
149+
150+
#region GetOriginalObjectIndex
151+
152+
[Fact]
153+
public void GetOriginalObjectIndex_ReturnsCorrectIndex()
154+
{
155+
var ds = CreateTestDataSource();
156+
157+
Assert.Equal(0, ds.GetOriginalObjectIndex(0));
158+
Assert.Equal(1, ds.GetOriginalObjectIndex(1));
159+
Assert.Equal(2, ds.GetOriginalObjectIndex(2));
160+
}
161+
162+
[Fact]
163+
public void GetOriginalObjectIndex_OutOfBounds_ReturnsNegativeOne()
164+
{
165+
var ds = CreateTestDataSource();
166+
167+
Assert.Equal(-1, ds.GetOriginalObjectIndex(-1));
168+
Assert.Equal(-1, ds.GetOriginalObjectIndex(99));
169+
}
170+
171+
#endregion
172+
173+
#region Filter
174+
175+
[Fact]
176+
public void Filter_EmptyPattern_ReturnsSelf()
177+
{
178+
var ds = CreateTestDataSource();
179+
180+
var filtered = ds.Filter("");
181+
Assert.Same(ds, filtered);
182+
}
183+
184+
[Fact]
185+
public void Filter_NullPattern_ReturnsSelf()
186+
{
187+
var ds = CreateTestDataSource();
188+
189+
var filtered = ds.Filter(null!);
190+
Assert.Same(ds, filtered);
191+
}
192+
193+
[Fact]
194+
public void Filter_MatchesSubstring_ReturnsMatchingRows()
195+
{
196+
var ds = CreateTestDataSource();
197+
198+
var filtered = ds.Filter("Ali");
199+
Assert.Equal(1, filtered.Rows);
200+
Assert.Equal("Alice", filtered[0, 0]);
201+
}
202+
203+
[Fact]
204+
public void Filter_CaseInsensitive()
205+
{
206+
var ds = CreateTestDataSource();
207+
208+
var filtered = ds.Filter("alice");
209+
Assert.Equal(1, filtered.Rows);
210+
Assert.Equal("Alice", filtered[0, 0]);
211+
}
212+
213+
[Fact]
214+
public void Filter_Regex_MatchesPattern()
215+
{
216+
var ds = CreateTestDataSource();
217+
218+
var filtered = ds.Filter("^[AB]");
219+
Assert.Equal(2, filtered.Rows);
220+
}
221+
222+
[Fact]
223+
public void Filter_NoMatch_ReturnsEmpty()
224+
{
225+
var ds = CreateTestDataSource();
226+
227+
var filtered = ds.Filter("ZZZ");
228+
Assert.Equal(0, filtered.Rows);
229+
}
230+
231+
[Fact]
232+
public void Filter_MatchesAnyColumn()
233+
{
234+
var ds = CreateTestDataSource();
235+
236+
// "3" is in the Id column of Charlie's row
237+
var filtered = ds.Filter("^3$");
238+
Assert.Equal(1, filtered.Rows);
239+
Assert.Equal("Charlie", filtered[0, 0]);
240+
}
241+
242+
[Fact]
243+
public void Filter_PreservesOriginalObjectIndex()
244+
{
245+
var ds = CreateTestDataSource();
246+
247+
var filtered = ds.Filter("Bob");
248+
Assert.Equal(1, filtered.Rows);
249+
Assert.Equal(2, filtered.GetOriginalObjectIndex(0)); // Bob was at index 2
250+
}
251+
252+
[Fact]
253+
public void Filter_InvalidRegex_ThrowsRegexParseException()
254+
{
255+
var ds = CreateTestDataSource();
256+
257+
Assert.ThrowsAny<System.Text.RegularExpressions.RegexParseException>(() => ds.Filter("[invalid"));
258+
}
259+
260+
#endregion
261+
262+
#region Sort
263+
264+
[Fact]
265+
public void Sort_Ascending_ByName()
266+
{
267+
var ds = CreateTestDataSource(); // Charlie, Alice, Bob
268+
269+
var sorted = ds.Sort(0, descending: false);
270+
271+
Assert.Equal("Alice", sorted[0, 0]);
272+
Assert.Equal("Bob", sorted[1, 0]);
273+
Assert.Equal("Charlie", sorted[2, 0]);
274+
}
275+
276+
[Fact]
277+
public void Sort_Descending_ByName()
278+
{
279+
var ds = CreateTestDataSource();
280+
281+
var sorted = ds.Sort(0, descending: true);
282+
283+
Assert.Equal("Charlie", sorted[0, 0]);
284+
Assert.Equal("Bob", sorted[1, 0]);
285+
Assert.Equal("Alice", sorted[2, 0]);
286+
}
287+
288+
[Fact]
289+
public void Sort_NumericAware_ById()
290+
{
291+
var columns = CreateColumns("Name", "Id");
292+
var ds = new OutTableViewDataSource(columns);
293+
ds.AddRow(CreateRow(columns, 0, "A", "10"));
294+
ds.AddRow(CreateRow(columns, 1, "B", "2"));
295+
ds.AddRow(CreateRow(columns, 2, "C", "1"));
296+
297+
var sorted = ds.Sort(1, descending: false);
298+
299+
// Numeric sort: 1, 2, 10 (not lexicographic: 1, 10, 2)
300+
Assert.Equal("1", sorted[0, 1]);
301+
Assert.Equal("2", sorted[1, 1]);
302+
Assert.Equal("10", sorted[2, 1]);
303+
}
304+
305+
[Fact]
306+
public void Sort_PreservesOriginalObjectIndex()
307+
{
308+
var ds = CreateTestDataSource(); // Charlie(0), Alice(1), Bob(2)
309+
310+
var sorted = ds.Sort(0, descending: false); // Alice, Bob, Charlie
311+
312+
Assert.Equal(1, sorted.GetOriginalObjectIndex(0)); // Alice was index 1
313+
Assert.Equal(2, sorted.GetOriginalObjectIndex(1)); // Bob was index 2
314+
Assert.Equal(0, sorted.GetOriginalObjectIndex(2)); // Charlie was index 0
315+
}
316+
317+
[Fact]
318+
public void Sort_AddsAscendingGlyph()
319+
{
320+
var ds = CreateTestDataSource();
321+
322+
var sorted = ds.Sort(0, descending: false);
323+
324+
Assert.Equal("Name ▲", sorted.ColumnNames[0]);
325+
Assert.Equal("Id", sorted.ColumnNames[1]); // Other columns unchanged
326+
}
327+
328+
[Fact]
329+
public void Sort_AddsDescendingGlyph()
330+
{
331+
var ds = CreateTestDataSource();
332+
333+
var sorted = ds.Sort(0, descending: true);
334+
335+
Assert.Equal("Name ▼", sorted.ColumnNames[0]);
336+
}
337+
338+
[Fact]
339+
public void Sort_InvalidColumnIndex_ReturnsSelf()
340+
{
341+
var ds = CreateTestDataSource();
342+
343+
Assert.Same(ds, ds.Sort(-1, false));
344+
Assert.Same(ds, ds.Sort(99, false));
345+
}
346+
347+
[Fact]
348+
public void Sort_DoesNotMutateOriginal()
349+
{
350+
var ds = CreateTestDataSource();
351+
352+
var sorted = ds.Sort(0, descending: false);
353+
354+
// Original still has Charlie first
355+
Assert.Equal("Charlie", ds[0, 0]);
356+
// Sorted has Alice first
357+
Assert.Equal("Alice", sorted[0, 0]);
358+
}
359+
360+
#endregion
361+
362+
#region GetColumns
363+
364+
[Fact]
365+
public void GetColumns_ReturnsSameColumnList()
366+
{
367+
var columns = CreateColumns("A", "B");
368+
var ds = new OutTableViewDataSource(columns);
369+
370+
Assert.Same(columns, ds.GetColumns());
371+
}
372+
373+
#endregion
374+
}

0 commit comments

Comments
 (0)