Skip to content

Commit 36a0601

Browse files
csharpfritzCopilot
andcommitted
feat(events): Implement P1 event handler improvements
P1-1: Button OnClick EventArgs P1-2: Sender property on 26 EventArgs P1-3: GridView PageIndexChanging P1-4: GridView RowUpdated/RowDeleted P1-5: GridView SelectedIndexChanged type P1-7: SelectMethod lambda-friendly P1-8: SelectMethodAsync P1-9: TreeNodePopulate P1-10: TreeView bare aliases P1-11: DataGrid bare aliases Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2db2fb1 commit 36a0601

44 files changed

Lines changed: 1533 additions & 24 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
@code {
2+
3+
// ─── P1-1: Button OnClick fires with EventArgs (not MouseEventArgs) ─────
4+
// The OnClick parameter should be EventCallback<EventArgs> and the handler
5+
// should receive EventArgs.Empty when the button is clicked.
6+
7+
EventArgs _receivedArgs;
8+
bool _clickFired;
9+
10+
[Fact]
11+
public void Button_OnClick_FiresWithEventArgs()
12+
{
13+
_receivedArgs = null;
14+
_clickFired = false;
15+
16+
var cut = Render(@<Button OnClick="@((EventArgs e) => { _receivedArgs = e; _clickFired = true; })">Click</Button>);
17+
18+
cut.Find("input").Click();
19+
20+
_clickFired.ShouldBeTrue("OnClick should fire when button is clicked");
21+
_receivedArgs.ShouldNotBeNull("EventArgs should not be null");
22+
}
23+
24+
[Fact]
25+
public void Button_OnClick_ReceivesEventArgsEmpty()
26+
{
27+
_receivedArgs = null;
28+
29+
var cut = Render(@<Button OnClick="@((EventArgs e) => _receivedArgs = e)">Click</Button>);
30+
31+
cut.Find("input").Click();
32+
33+
_receivedArgs.ShouldBe(EventArgs.Empty, "Handler should receive EventArgs.Empty");
34+
}
35+
36+
[Fact]
37+
public void Button_Renders_And_OnClick_Invokes()
38+
{
39+
var content = "not clicked";
40+
41+
var cut = Render(@<Button OnClick="@((EventArgs e) => content = "clicked")" Text="Go" />);
42+
43+
// Button renders as an input element
44+
var input = cut.Find("input");
45+
input.ShouldNotBeNull();
46+
47+
input.Click();
48+
content.ShouldBe("clicked");
49+
}
50+
51+
[Fact]
52+
public void Button_OnClick_ParameterType_IsEventArgs_NotMouseEventArgs()
53+
{
54+
// Compile-time check: if this compiles, the parameter type is EventCallback<EventArgs>
55+
EventCallback<EventArgs> handler = EventCallback.Factory.Create<EventArgs>(this, (EventArgs e) => { });
56+
57+
var cut = Render<Button>(p => p
58+
.Add(ps => ps.OnClick, handler)
59+
.Add(ps => ps.Text, "Test")
60+
);
61+
62+
cut.Instance.ShouldNotBeNull();
63+
}
64+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
@using BlazorWebFormsComponents.DataBinding
2+
3+
@code {
4+
5+
// ─── P1-7: SelectItems lambda-friendly parameter ────────────────────
6+
// SelectItems should accept Func<IEnumerable<T>> (no out param) as an
7+
// alternative to the SelectMethod delegate that requires out totalRowCount.
8+
// SelectItems should take precedence over SelectMethod when both are set.
9+
10+
int _selectMethodCallCount;
11+
int _selectItemsCallCount;
12+
13+
[Fact]
14+
public void SelectItems_Works_With_Lambda()
15+
{
16+
_selectItemsCallCount = 0;
17+
var widgets = Widget.SimpleWidgetList.Take(3).ToArray();
18+
19+
var cut = Render(
20+
@<GridView ItemType="Widget" AutoGenerateColumns="false"
21+
SelectItems="@((int maxRows, int startRowIndex, string sortBy) => { _selectItemsCallCount++; return widgets.AsEnumerable(); })">
22+
<Columns>
23+
<BoundField ItemType="Widget" DataField="Name" HeaderText="Name" />
24+
</Columns>
25+
</GridView>
26+
);
27+
28+
_selectItemsCallCount.ShouldBeGreaterThanOrEqualTo(1, "SelectItems lambda should be invoked");
29+
cut.FindAll("tbody tr").Count.ShouldBe(3, "Data from SelectItems should render");
30+
}
31+
32+
[Fact]
33+
public void SelectItems_Takes_Precedence_Over_SelectMethod()
34+
{
35+
_selectMethodCallCount = 0;
36+
_selectItemsCallCount = 0;
37+
var itemsData = new[] { new Widget { Id = 100, Name = "FromItems" } };
38+
39+
var cut = Render(
40+
@<GridView ItemType="Widget" AutoGenerateColumns="false"
41+
SelectItems="@((int maxRows, int startRowIndex, string sortBy) => { _selectItemsCallCount++; return itemsData.AsEnumerable(); })"
42+
SelectMethod="GetWidgets">
43+
<Columns>
44+
<BoundField ItemType="Widget" DataField="Name" HeaderText="Name" />
45+
</Columns>
46+
</GridView>
47+
);
48+
49+
_selectItemsCallCount.ShouldBeGreaterThanOrEqualTo(1, "SelectItems should be invoked");
50+
_selectMethodCallCount.ShouldBe(0, "SelectMethod should NOT be invoked when SelectItems is set");
51+
52+
// Verify data came from SelectItems
53+
cut.Find("tbody tr td").TextContent.ShouldContain("FromItems");
54+
}
55+
56+
[Fact]
57+
public void SelectItems_Works_With_Repeater()
58+
{
59+
var items = new[] { new Widget { Id = 1, Name = "RepA" }, new Widget { Id = 2, Name = "RepB" } };
60+
61+
var cut = Render(
62+
@<Repeater ItemType="Widget" Context="Item"
63+
SelectItems="@((int maxRows, int startRowIndex, string sortBy) => items.AsEnumerable())">
64+
<ItemTemplate><span class="rep-item">@Item.Name</span></ItemTemplate>
65+
</Repeater>
66+
);
67+
68+
cut.FindAll(".rep-item").Count.ShouldBe(2, "Repeater should render items from SelectItems");
69+
}
70+
71+
IQueryable<Widget> GetWidgets(int maxRows, int startRowIndex, string sortByExpression, out int totalRowCount)
72+
{
73+
_selectMethodCallCount++;
74+
totalRowCount = Widget.SimpleWidgetList.Length;
75+
return Widget.SimpleWidgetList.AsQueryable();
76+
}
77+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
@using BlazorWebFormsComponents.DataBinding
2+
3+
@code {
4+
5+
// ─── P1-8: SelectMethodAsync parameter ──────────────────────────────
6+
// SelectMethodAsync should accept Func<Task<IEnumerable<T>>> for async data loading.
7+
// It should take precedence over synchronous SelectMethod and SelectItems.
8+
9+
int _syncCallCount;
10+
int _asyncCallCount;
11+
12+
[Fact]
13+
public void SelectMethodAsync_Works_WithAsyncDataLoading()
14+
{
15+
_asyncCallCount = 0;
16+
var widgets = Widget.SimpleWidgetList.Take(4).ToArray();
17+
18+
var cut = Render(
19+
@<GridView ItemType="Widget" AutoGenerateColumns="false"
20+
SelectMethodAsync="@(async (int maxRows, int startRowIndex, string sortBy) => { _asyncCallCount++; await Task.Yield(); return widgets.AsEnumerable(); })">
21+
<Columns>
22+
<BoundField ItemType="Widget" DataField="Name" HeaderText="Name" />
23+
</Columns>
24+
</GridView>
25+
);
26+
27+
// Wait for async data to load and re-render
28+
cut.WaitForState(() => cut.FindAll("tbody tr").Count > 1, TimeSpan.FromSeconds(2));
29+
30+
_asyncCallCount.ShouldBeGreaterThanOrEqualTo(1, "SelectMethodAsync should be invoked");
31+
cut.FindAll("tbody tr").Count.ShouldBe(4, "Data from SelectMethodAsync should render");
32+
}
33+
34+
[Fact]
35+
public void SelectMethodAsync_Takes_Precedence_Over_SyncSelectMethod()
36+
{
37+
_syncCallCount = 0;
38+
_asyncCallCount = 0;
39+
var asyncData = new[] { new Widget { Id = 200, Name = "AsyncItem" } };
40+
41+
var cut = Render(
42+
@<GridView ItemType="Widget" AutoGenerateColumns="false"
43+
SelectMethodAsync="@(async (int maxRows, int startRowIndex, string sortBy) => { _asyncCallCount++; await Task.Yield(); return asyncData.AsEnumerable(); })"
44+
SelectMethod="GetWidgetsSync">
45+
<Columns>
46+
<BoundField ItemType="Widget" DataField="Name" HeaderText="Name" />
47+
</Columns>
48+
</GridView>
49+
);
50+
51+
// Wait for async data to load
52+
cut.WaitForState(() => _asyncCallCount >= 1, TimeSpan.FromSeconds(2));
53+
54+
_asyncCallCount.ShouldBeGreaterThanOrEqualTo(1, "SelectMethodAsync should be invoked");
55+
_syncCallCount.ShouldBe(0, "Sync SelectMethod should NOT be invoked when async is set");
56+
57+
cut.Find("tbody tr td").TextContent.ShouldContain("AsyncItem");
58+
}
59+
60+
[Fact]
61+
public void SelectMethodAsync_Takes_Precedence_Over_SelectItems()
62+
{
63+
_asyncCallCount = 0;
64+
var syncItems = new[] { new Widget { Id = 1, Name = "SyncItem" } };
65+
var asyncData = new[] { new Widget { Id = 2, Name = "AsyncItem" } };
66+
67+
var cut = Render(
68+
@<GridView ItemType="Widget" AutoGenerateColumns="false"
69+
SelectMethodAsync="@(async (int maxRows, int startRowIndex, string sortBy) => { _asyncCallCount++; await Task.Yield(); return asyncData.AsEnumerable(); })"
70+
SelectItems="@((int maxRows, int startRowIndex, string sortBy) => syncItems.AsEnumerable())">
71+
<Columns>
72+
<BoundField ItemType="Widget" DataField="Name" HeaderText="Name" />
73+
</Columns>
74+
</GridView>
75+
);
76+
77+
// Wait for async data to load
78+
cut.WaitForState(() => _asyncCallCount >= 1, TimeSpan.FromSeconds(2));
79+
80+
_asyncCallCount.ShouldBeGreaterThanOrEqualTo(1);
81+
cut.Find("tbody tr td").TextContent.ShouldContain("AsyncItem");
82+
}
83+
84+
IQueryable<Widget> GetWidgetsSync(int maxRows, int startRowIndex, string sortByExpression, out int totalRowCount)
85+
{
86+
_syncCallCount++;
87+
totalRowCount = Widget.SimpleWidgetList.Length;
88+
return Widget.SimpleWidgetList.AsQueryable();
89+
}
90+
}

0 commit comments

Comments
 (0)