Skip to content

Commit 86c9ff7

Browse files
Cleanup copilot code, add option to use old behaviour, added tests.
1 parent 0b0f2e9 commit 86c9ff7

File tree

5 files changed

+210
-130
lines changed

5 files changed

+210
-130
lines changed

SimpleBlazorMultiselect.Tests/SimpleMultiselectTests.cs

Lines changed: 187 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ namespace SimpleBlazorMultiselect.Tests;
1111
public class SimpleMultiselectTests : TestContext
1212
{
1313
private readonly List<string> _testOptions = new() { "Apple", "Banana", "Cherry", "Date", "Elderberry" };
14-
14+
1515
public SimpleMultiselectTests()
1616
{
1717
JSInterop.SetupModule("./_content/SimpleBlazorMultiselect/js/simpleMultiselect.js")
1818
.SetupModule("register", invocation => invocation.Arguments.Count == 2)
1919
.SetupVoid("dispose");
2020
}
21-
21+
2222
[Fact]
2323
public void Component_RendersWithDefaultText_WhenNoOptionsSelected()
2424
{
@@ -67,7 +67,7 @@ public void Component_ShowsAllOptions_WhenDropdownIsOpen()
6767

6868
var dropdownItems = component.FindAll(".dropdown-item");
6969
dropdownItems.Should().HaveCount(_testOptions.Count);
70-
70+
7171
foreach (var option in _testOptions)
7272
{
7373
component.Markup.Should().Contain(option);
@@ -81,17 +81,14 @@ public void Component_SelectsOption_WhenOptionClicked()
8181
var component = RenderComponent<SimpleMultiselect<string>>(parameters => parameters
8282
.Add(p => p.Options, _testOptions)
8383
.Add(p => p.SelectedOptions, selectedOptions)
84-
.Add(p => p.SelectedOptionsChanged, EventCallback.Factory.Create<HashSet<string>>(this, newSelection =>
85-
{
86-
selectedOptions = newSelection;
87-
})));
84+
.Add(p => p.SelectedOptionsChanged, EventCallback.Factory.Create<HashSet<string>>(this, newSelection => { selectedOptions = newSelection; })));
8885

8986
var button = component.Find("button");
9087
button.Click();
91-
88+
9289
var firstOption = component.FindAll(".dropdown-item")[0];
9390
firstOption.Click();
94-
91+
9592
firstOption = component.FindAll(".dropdown-item")[0];
9693
var checkbox = firstOption.QuerySelector<IHtmlInputElement>("input[type='checkbox']");
9794
checkbox.Should().NotBeNull();
@@ -106,19 +103,16 @@ public void Component_DeselectsOption_WhenSelectedOptionClicked()
106103
var component = RenderComponent<SimpleMultiselect<string>>(parameters => parameters
107104
.Add(p => p.Options, _testOptions)
108105
.Add(p => p.SelectedOptions, selectedOptions)
109-
.Add(p => p.SelectedOptionsChanged, EventCallback.Factory.Create<HashSet<string>>(this, newSelection =>
110-
{
111-
selectedOptions = newSelection;
112-
})));
106+
.Add(p => p.SelectedOptionsChanged, EventCallback.Factory.Create<HashSet<string>>(this, newSelection => { selectedOptions = newSelection; })));
113107

114108
var button = component.Find("button");
115109
button.Click();
116-
110+
117111
var firstOption = component.FindAll(".dropdown-item")[0];
118112
firstOption.Click();
119113

120114
selectedOptions.Should().NotContain("Apple");
121-
foreach(var option in component.FindAll(".dropdown-item"))
115+
foreach (var option in component.FindAll(".dropdown-item"))
122116
{
123117
var cb = option.QuerySelector<IHtmlInputElement>("input[type='checkbox']");
124118
cb!.IsChecked.Should().BeFalse();
@@ -149,7 +143,7 @@ public void Component_FiltersOptions_WhenFilterTextEntered()
149143

150144
var button = component.Find("button");
151145
button.Click();
152-
146+
153147
var filterInput = component.Find(".simple-filter-input");
154148
filterInput.Input("App");
155149

@@ -161,13 +155,13 @@ public void Component_FiltersOptions_WhenFilterTextEntered()
161155
[Fact]
162156
public void Component_UsesCustomStringSelector_WhenProvided()
163157
{
164-
var complexOptions = new List<TestItem>
158+
var complexOptions = new List<TestValueItem>
165159
{
166160
new("1", "Apple"),
167161
new("2", "Banana")
168162
};
169163

170-
var component = RenderComponent<SimpleMultiselect<TestItem>>(parameters => parameters
164+
var component = RenderComponent<SimpleMultiselect<TestValueItem>>(parameters => parameters
171165
.Add(p => p.Options, complexOptions)
172166
.Add(p => p.StringSelector, item => item.Name));
173167

@@ -188,7 +182,7 @@ public void Component_UsesCustomFilterPredicate_WhenProvided()
188182

189183
var button = component.Find("button");
190184
button.Click();
191-
185+
192186
var filterInput = component.Find(".simple-filter-input");
193187
filterInput.Input("B");
194188

@@ -218,22 +212,19 @@ public void Component_SingleSelectMode_SelectsOnlyOneOption()
218212
.Add(p => p.Options, _testOptions)
219213
.Add(p => p.SelectedOptions, selectedOptions)
220214
.Add(p => p.IsMultiSelect, false)
221-
.Add(p => p.SelectedOptionsChanged, EventCallback.Factory.Create<HashSet<string>>(this, newSelection =>
222-
{
223-
selectedOptions = newSelection;
224-
})));
215+
.Add(p => p.SelectedOptionsChanged, EventCallback.Factory.Create<HashSet<string>>(this, newSelection => { selectedOptions = newSelection; })));
225216

226217
var button = component.Find("button");
227218
button.Click();
228-
219+
229220
var firstOption = component.FindAll(".dropdown-item")[0];
230221
firstOption.Click();
231222
component.Render();
232-
223+
233224
JSInterop.VerifyInvoke("dispose");
234225
component.Instance.IsDropdownOpen.Should().BeFalse();
235-
236-
button.Click();
226+
227+
button.Click();
237228
var secondOption = component.FindAll(".dropdown-item")[1];
238229
secondOption.Click();
239230

@@ -295,10 +286,10 @@ public void Component_CachesFilteredOptions_ForPerformance()
295286

296287
var button = component.Find("button");
297288
button.Click();
298-
289+
299290
var filterInput = component.Find(".simple-filter-input");
300291
filterInput.Input("Appl");
301-
292+
302293
// Trigger multiple renders without changing filter
303294
component.Render();
304295
component.Render();
@@ -307,6 +298,171 @@ public void Component_CachesFilteredOptions_ForPerformance()
307298
dropdownItems.Should().HaveCount(1);
308299
dropdownItems[0].TextContent.Should().Contain("Apple");
309300
}
310-
311-
private record TestItem(string Id, string Name);
301+
302+
[Fact]
303+
public void Component_CanDeselect_WhenPrefilledValueItems()
304+
{
305+
var options = new List<TestValueItem>
306+
{
307+
new("1", "Apple"),
308+
new("2", "Banana"),
309+
new("3", "Cherry")
310+
};
311+
var selectedItems = new HashSet<TestValueItem>
312+
{
313+
new("1", "Apple")
314+
};
315+
316+
var component = RenderComponent<SimpleMultiselect<TestValueItem>>(parameters => parameters
317+
.Add(p => p.Options, options)
318+
.Add(p => p.SelectedOptions, selectedItems)
319+
.Add(p => p.StringSelector, item => item.Name)
320+
.Add(p => p.DefaultText, "Select fruits")
321+
.Add(p => p.SelectedOptionsChanged, EventCallback.Factory.Create<HashSet<TestValueItem>>(this, newSelection => { selectedItems = newSelection; })));
322+
323+
var button = component.Find("button");
324+
button.TextContent.Should().Contain("Apple");
325+
button.Click();
326+
327+
// Now only apple should be checked
328+
var appleOption = component.FindAll(".dropdown-item")[0];
329+
var appleCheckbox = appleOption.QuerySelector<IHtmlInputElement>("input[type='checkbox']");
330+
appleCheckbox.Should().NotBeNull();
331+
appleCheckbox.IsChecked.Should().BeTrue();
332+
333+
appleOption.Click();
334+
335+
// After clicking, apple should be deselected
336+
selectedItems.Should().BeEmpty();
337+
button = component.Find("button");
338+
button.TextContent.Should().Be("Select fruits");
339+
}
340+
341+
[Fact]
342+
public void Component_CanDeselect_WhenPrefilledReferenceItems()
343+
{
344+
var options = new List<TestReferenceItem>
345+
{
346+
new("1", "Apple"),
347+
new("2", "Banana"),
348+
new("3", "Cherry")
349+
};
350+
var selectedItems = new HashSet<TestReferenceItem>
351+
{
352+
new("1", "Apple")
353+
};
354+
355+
var component = RenderComponent<SimpleMultiselect<TestReferenceItem>>(parameters => parameters
356+
.Add(p => p.Options, options)
357+
.Add(p => p.SelectedOptions, selectedItems)
358+
.Add(p => p.StringSelector, item => item.Name)
359+
.Add(p => p.DefaultText, "Select fruits")
360+
.Add(p => p.SelectedOptionsChanged, EventCallback.Factory.Create<HashSet<TestReferenceItem>>(this, newSelection => { selectedItems = newSelection; })));
361+
362+
var button = component.Find("button");
363+
button.TextContent.Should().Contain("Apple");
364+
button.Click();
365+
366+
// Now only apple should be checked
367+
var appleOption = component.FindAll(".dropdown-item")[0];
368+
var appleCheckbox = appleOption.QuerySelector<IHtmlInputElement>("input[type='checkbox']");
369+
appleCheckbox.Should().NotBeNull();
370+
appleCheckbox.IsChecked.Should().BeTrue();
371+
372+
appleOption.Click();
373+
374+
// After clicking, apple should be deselected
375+
selectedItems.Should().BeEmpty();
376+
button = component.Find("button");
377+
button.TextContent.Should().Be("Select fruits");
378+
}
379+
380+
[Fact]
381+
public void Component_CanDeselectValueItem_WhenMatchByReference()
382+
{
383+
var options = new List<TestValueItem>
384+
{
385+
new("1", "Apple"),
386+
new("2", "Banana"),
387+
new("3", "Cherry")
388+
};
389+
var selectedItems = new HashSet<TestValueItem>
390+
{
391+
new("1", "Apple")
392+
};
393+
394+
var component = RenderComponent<SimpleMultiselect<TestValueItem>>(parameters => parameters
395+
.Add(p => p.Options, options)
396+
.Add(p => p.SelectedOptions, selectedItems)
397+
.Add(p => p.StringSelector, item => item.Name)
398+
.Add(p => p.DefaultText, "Select fruits")
399+
.Add(p => p.SelectedOptionsChanged, EventCallback.Factory.Create<HashSet<TestValueItem>>(this, newSelection => { selectedItems = newSelection; }))
400+
.Add(p => p.MatchByReference, true)); // Should not matter for value types
401+
402+
var button = component.Find("button");
403+
button.TextContent.Should().Contain("Apple");
404+
button.Click();
405+
406+
// Now only apple should be checked
407+
var appleOption = component.FindAll(".dropdown-item")[0];
408+
var appleCheckbox = appleOption.QuerySelector<IHtmlInputElement>("input[type='checkbox']");
409+
appleCheckbox.Should().NotBeNull();
410+
appleCheckbox.IsChecked.Should().BeTrue();
411+
412+
appleOption.Click();
413+
414+
// After clicking, apple should be deselected
415+
selectedItems.Should().BeEmpty();
416+
button = component.Find("button");
417+
button.TextContent.Should().Be("Select fruits");
418+
}
419+
420+
[Fact]
421+
public void Component_CannotDeselectIdenticalInstance_WhenMatchByReference()
422+
{
423+
var options = new List<TestReferenceItem>
424+
{
425+
new("1", "Apple"),
426+
new("2", "Banana"),
427+
new("3", "Cherry")
428+
};
429+
var selectedItems = new HashSet<TestReferenceItem>
430+
{
431+
new("1", "Apple")
432+
};
433+
434+
var component = RenderComponent<SimpleMultiselect<TestReferenceItem>>(parameters => parameters
435+
.Add(p => p.Options, options)
436+
.Add(p => p.SelectedOptions, selectedItems)
437+
.Add(p => p.StringSelector, item => item.Name)
438+
.Add(p => p.DefaultText, "Select fruits")
439+
.Add(p => p.SelectedOptionsChanged, EventCallback.Factory.Create<HashSet<TestReferenceItem>>(this, newSelection => { selectedItems = newSelection; }))
440+
.Add(p => p.MatchByReference, true)); // This will break the deselection
441+
442+
var button = component.Find("button");
443+
button.TextContent.Should().Contain("Apple");
444+
button.Click();
445+
446+
// Apple should not be checked because the instance is different
447+
// So clicking it will add another apple instead of removing the existing one
448+
var appleOption = component.FindAll(".dropdown-item")[0];
449+
var appleCheckbox = appleOption.QuerySelector<IHtmlInputElement>("input[type='checkbox']");
450+
appleCheckbox.Should().NotBeNull();
451+
appleCheckbox.IsChecked.Should().BeFalse();
452+
453+
appleOption.Click();
454+
455+
// After clicking, we should have two apples
456+
selectedItems.Should().HaveCount(2);
457+
button = component.Find("button");
458+
button.TextContent.Should().Be("Apple, Apple");
459+
}
460+
461+
private record TestValueItem(string Id, string Name);
462+
463+
private class TestReferenceItem(string id, string name)
464+
{
465+
public string Id { get; set; } = id;
466+
public string Name { get; set; } = name;
467+
}
312468
}

src/SimpleBlazorMultiselect.Demo/Layout/NavMenu.razor

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,5 @@
7676
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> ObjectBinding
7777
</NavLink>
7878
</div>
79-
80-
<!-- Prefilled Options Test -->
81-
<div class="nav-item px-3">
82-
<NavLink class="nav-link" href="PrefilledOptions" Match="NavLinkMatch.All">
83-
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Prefilled Options Test
84-
</NavLink>
85-
</div>
8679
</nav>
8780
</div>

0 commit comments

Comments
 (0)