Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 24 additions & 11 deletions src/Sharprompt/Forms/MultiSelectForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,32 +124,45 @@ private bool HandleSpacebar()

private bool HandleCtrlA()
{
if (_selectedItems.Count == Paginator.TotalCount)
// Bulk operations only apply to the currently filtered items, so selections
// hidden by the filter are preserved.
var selectedItems = new HashSet<T>(_selectedItems);

if (Paginator.All(_selectedItems.Contains))
{
_selectedItems.Clear();
selectedItems.ExceptWith(Paginator);
}
else
{
foreach (var item in Paginator)
{
_selectedItems.Add(item);
}
selectedItems.UnionWith(Paginator);
}

UpdateSelectedItems(selectedItems);

return true;
}

private bool HandleCtrlI()
{
var invertedItems = Paginator.Except(_selectedItems).ToArray();
var selectedItems = new HashSet<T>(_selectedItems);

_selectedItems.Clear();
selectedItems.SymmetricExceptWith(Paginator);

UpdateSelectedItems(selectedItems);

foreach (var item in invertedItems)
return true;
}

private void UpdateSelectedItems(HashSet<T> selectedItems)
{
if (selectedItems.Count > _options.Maximum)
{
_selectedItems.Add(item);
SetError(string.Format(Resource.Validation_Maximum_SelectionRequired, _options.Maximum));

return;
}

return true;
_selectedItems.Clear();
_selectedItems.UnionWith(selectedItems);
}
}
4 changes: 3 additions & 1 deletion src/Sharprompt/Internal/Paginator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ public void PreviousItem()
{
if (_selectedIndex <= 0)
{
if (!LoopingSelection)
// With nothing selected, select the last item on the current page
// instead of moving to the previous page, mirroring NextItem.
if (!LoopingSelection && _selectedIndex == 0)
{
PreviousPage();
}
Expand Down
164 changes: 164 additions & 0 deletions tests/Sharprompt.Tests/Forms/FormInteractionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,170 @@ public void MultiSelectForm_SpacebarToggle_ReturnsCheckedItems()
Assert.Equal(new[] { "apple", "banana" }, form.Start());
}

[Fact]
public void MultiSelectForm_CtrlA_SelectsAllItems()
{
var (driver, configuration) = CreateTestContext();

driver.EnqueueKey(ConsoleKey.A, ConsoleModifiers.Control, '\x01');
driver.EnqueueEnter();

var options = new MultiSelectOptions<string>
{
Message = "message",
Items = ["apple", "banana", "cherry"]
};

using var form = new MultiSelectForm<string>(options, configuration);

Assert.Equal(new[] { "apple", "banana", "cherry" }, form.Start());
}

[Fact]
public void MultiSelectForm_CtrlA_AllSelected_DeselectsAllItems()
{
var (driver, configuration) = CreateTestContext();

driver.EnqueueKey(ConsoleKey.A, ConsoleModifiers.Control, '\x01');
driver.EnqueueEnter();

var options = new MultiSelectOptions<string>
{
Message = "message",
Items = ["apple", "banana", "cherry"],
DefaultValues = ["apple", "banana", "cherry"],
Minimum = 0
};

using var form = new MultiSelectForm<string>(options, configuration);

Assert.Empty(form.Start());
}

[Fact]
public void MultiSelectForm_CtrlA_ExceedingMaximum_KeepsSelection()
{
var (driver, configuration) = CreateTestContext();

driver.EnqueueKey(ConsoleKey.A, ConsoleModifiers.Control, '\x01');
driver.EnqueueEnter();

var options = new MultiSelectOptions<string>
{
Message = "message",
Items = ["apple", "banana", "cherry"],
DefaultValues = ["apple"],
Maximum = 2
};

using var form = new MultiSelectForm<string>(options, configuration);

Assert.Equal(new[] { "apple" }, form.Start());
}

[Fact]
public void MultiSelectForm_CtrlA_WithFilter_SelectsOnlyFilteredItems()
{
var (driver, configuration) = CreateTestContext();

driver.EnqueueText("b");
driver.EnqueueKey(ConsoleKey.A, ConsoleModifiers.Control, '\x01');
driver.EnqueueEnter();

var options = new MultiSelectOptions<string>
{
Message = "message",
Items = ["apple", "banana", "cherry", "blueberry"]
};

using var form = new MultiSelectForm<string>(options, configuration);

Assert.Equal(new[] { "banana", "blueberry" }, form.Start());
}

[Fact]
public void MultiSelectForm_CtrlA_WithFilter_DeselectsOnlyFilteredItems()
{
var (driver, configuration) = CreateTestContext();

driver.EnqueueText("b");
driver.EnqueueKey(ConsoleKey.A, ConsoleModifiers.Control, '\x01');
driver.EnqueueEnter();

var options = new MultiSelectOptions<string>
{
Message = "message",
Items = ["apple", "banana", "cherry", "blueberry"],
DefaultValues = ["apple", "banana", "blueberry"]
};

using var form = new MultiSelectForm<string>(options, configuration);

Assert.Equal(new[] { "apple" }, form.Start());
}

[Fact]
public void MultiSelectForm_CtrlI_InvertsSelection()
{
var (driver, configuration) = CreateTestContext();

driver.EnqueueKey(ConsoleKey.I, ConsoleModifiers.Control, '\t');
driver.EnqueueEnter();

var options = new MultiSelectOptions<string>
{
Message = "message",
Items = ["apple", "banana", "cherry"],
DefaultValues = ["apple"]
};

using var form = new MultiSelectForm<string>(options, configuration);

Assert.Equal(new[] { "banana", "cherry" }, form.Start());
}

[Fact]
public void MultiSelectForm_CtrlI_WithFilter_PreservesHiddenSelections()
{
var (driver, configuration) = CreateTestContext();

driver.EnqueueText("b");
driver.EnqueueKey(ConsoleKey.I, ConsoleModifiers.Control, '\t');
driver.EnqueueEnter();

var options = new MultiSelectOptions<string>
{
Message = "message",
Items = ["apple", "banana", "cherry", "blueberry"],
DefaultValues = ["apple", "banana"]
};

using var form = new MultiSelectForm<string>(options, configuration);

Assert.Equal(new[] { "apple", "blueberry" }, form.Start());
}

[Fact]
public void MultiSelectForm_CtrlI_ExceedingMaximum_KeepsSelection()
{
var (driver, configuration) = CreateTestContext();

driver.EnqueueKey(ConsoleKey.I, ConsoleModifiers.Control, '\t');
driver.EnqueueEnter();

var options = new MultiSelectOptions<string>
{
Message = "message",
Items = ["apple", "banana", "cherry"],
DefaultValues = ["apple"],
Maximum = 1
};

using var form = new MultiSelectForm<string>(options, configuration);

Assert.Equal(new[] { "apple" }, form.Start());
}

[Fact]
public void InputForm_TypedText_ReturnsConvertedValue()
{
Expand Down
33 changes: 33 additions & 0 deletions tests/Sharprompt.Tests/Internal/PaginatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,39 @@ public void PreviousItem()
Assert.Equal(0, selectedItem);
}

[Fact]
public void PreviousItem_NotSelected_SelectsLastItemOnCurrentPage()
{
var paginator = new Paginator<int>(Enumerable.Range(0, 20), 5, Optional<int>.Empty, x => x.ToString());
paginator.LoopingSelection = false;

paginator.NextPage();
paginator.PreviousItem();

var selected = paginator.TryGetSelectedItem(out var selectedItem);

Assert.True(selected);
Assert.Equal(9, selectedItem);
Assert.Equal(1, paginator.CurrentPage);
}

[Fact]
public void PreviousItem_FirstItem_MovesToPreviousPage()
{
var paginator = new Paginator<int>(Enumerable.Range(0, 20), 5, Optional<int>.Empty, x => x.ToString());
paginator.LoopingSelection = false;

paginator.NextPage();
paginator.NextItem();
paginator.PreviousItem();

var selected = paginator.TryGetSelectedItem(out var selectedItem);

Assert.True(selected);
Assert.Equal(4, selectedItem);
Assert.Equal(0, paginator.CurrentPage);
}

[Fact]
public void PreviousPage()
{
Expand Down
Loading