diff --git a/tests/Web.Tests.Bunit/Components/Features/Issues/IssuesPageFilterTests.cs b/tests/Web.Tests.Bunit/Components/Features/Issues/IssuesPageFilterTests.cs
new file mode 100644
index 0000000..e6e6c84
--- /dev/null
+++ b/tests/Web.Tests.Bunit/Components/Features/Issues/IssuesPageFilterTests.cs
@@ -0,0 +1,260 @@
+// ============================================
+// Copyright (c) 2026. All rights reserved.
+// File Name : IssuesPageFilterTests.cs
+// Company : mpaulosky
+// Author : Matthew Paulosky
+// Solution Name : IssueManager
+// Project Name : Web.Tests.Bunit
+// =============================================
+
+namespace Web.Components.Features.Issues;
+
+///
+/// bUnit tests for IssuesPage filter and search wiring.
+/// Verifies that filter values are correctly forwarded to .
+///
+/// Tests marked [Fact(Skip = "Pending #116")] depend on the filter-bug fix and/or
+/// the updated signature from issue #116:
+/// GetAllAsync(page, pageSize, searchTerm, authorName, statusName, categoryName, cancellationToken).
+///
+/// Closes #125. Depends on #116.
+///
+[ExcludeFromCodeCoverage]
+public class IssuesPageFilterTests : ComponentTestBase
+{
+ private readonly IIssueApiClient _mockIssueClient;
+
+ ///
+ /// Pre-seeded category for dropdown tests.
+ ///
+ private static readonly CategoryDto BugCategory = new(
+ ObjectId.GenerateNewId(),
+ "Bug",
+ "Bug category",
+ DateTime.UtcNow,
+ null,
+ false,
+ UserDto.Empty);
+
+ ///
+ /// Pre-seeded status for dropdown tests.
+ ///
+ private static readonly StatusDto OpenStatus = new(
+ ObjectId.GenerateNewId(),
+ "Open",
+ "Open status",
+ DateTime.UtcNow,
+ null,
+ false,
+ UserDto.Empty);
+
+ ///
+ /// Initializes a new instance of the class,
+ /// registering a mock and pre-seeding dropdown data.
+ ///
+ public IssuesPageFilterTests()
+ {
+ _mockIssueClient = Substitute.For();
+ _mockIssueClient
+ .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ .Returns(Task.FromResult(PaginatedResponse.Empty));
+ TestContext.Services.AddSingleton(_mockIssueClient);
+
+ // Seed category and status dropdown data so filters have selectable options.
+ // (ComponentTestBase registers the mocks; we reconfigure them here with real data.)
+ var categoryClient = TestContext.Services.GetRequiredService();
+ categoryClient.GetAllAsync(Arg.Any())
+ .Returns(Task.FromResult>([BugCategory]));
+
+ var statusClient = TestContext.Services.GetRequiredService();
+ statusClient.GetAllAsync(Arg.Any())
+ .Returns(Task.FromResult>([OpenStatus]));
+ }
+
+ ///
+ /// Verifies that on component mount GetAllAsync is called once with the default
+ /// parameters: page=1, pageSize=20, searchTerm=null, authorName=null.
+ ///
+ [Fact]
+ public void LoadIssues_OnInit_CallsApiWithDefaultParams()
+ {
+ // Act
+ TestContext.Render();
+
+ // Assert — exactly one call with the default/null filter values
+ _ = _mockIssueClient.Received(1)
+ .GetAllAsync(1, 20, null, null, null, null, Arg.Any());
+ }
+
+ ///
+ /// Verifies that typing a search term and clicking Search passes searchTerm
+ /// to .
+ ///
+ ///
+ /// Skipped pending issue #116: LoadIssues currently calls
+ /// GetAllAsync(page, 20) without forwarding _searchTerm.
+ /// Remove the Skip attribute once the component bug is fixed.
+ ///
+ [Fact(Skip = "Pending #116: LoadIssues does not yet forward _searchTerm to GetAllAsync")]
+ public async Task SearchBox_WhenFilled_PassesSearchTermToApi()
+ {
+ // Arrange
+ var cut = TestContext.Render();
+
+ // Act — type a search term then click Search
+ await cut.Find("#search").InputAsync(new ChangeEventArgs { Value = "my-bug" });
+ await cut.FindAll("button").First(b => b.TextContent.Trim() == "Search")
+ .ClickAsync(new MouseEventArgs());
+
+ // Assert — the second call (after init) should carry the search term
+ _ = _mockIssueClient.Received()
+ .GetAllAsync(1, 20, "my-bug", null, null, null, Arg.Any());
+ }
+
+ ///
+ /// Verifies that selecting a status from the dropdown and clicking Search
+ /// passes statusName to .
+ ///
+ ///
+ /// Skipped pending issue #116: IIssueApiClient.GetAllAsync does not yet
+ /// include a statusName parameter.
+ /// When #116 adds the parameter, update the assertion to:
+ /// GetAllAsync(1, 20, null, null, "Open", null, Arg.Any<CancellationToken>())
+ /// and remove the Skip attribute.
+ ///
+ [Fact(Skip = "Pending #116: IIssueApiClient.GetAllAsync does not yet include statusName parameter")]
+ public async Task StatusFilter_WhenSelected_PassesStatusNameToApi()
+ {
+ // Arrange
+ var cut = TestContext.Render();
+
+ // Act — select "Open" from the status filter, then click Search
+ await cut.Find("#status-filter").ChangeAsync(new ChangeEventArgs { Value = "Open" });
+ await cut.FindAll("button").First(b => b.TextContent.Trim() == "Search")
+ .ClickAsync(new MouseEventArgs());
+
+ // Assert
+ // TODO (#116): Replace with the specific statusName assertion once the parameter is added:
+ // _mockIssueClient.Received().GetAllAsync(1, 20, null, null, "Open", null, Arg.Any());
+ _ = _mockIssueClient.Received()
+ .GetAllAsync(1, 20, Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any());
+ }
+
+ ///
+ /// Verifies that selecting a category from the dropdown and clicking Search
+ /// passes categoryName to .
+ ///
+ ///
+ /// Skipped pending issue #116: IIssueApiClient.GetAllAsync does not yet
+ /// include a categoryName parameter.
+ /// When #116 adds the parameter, update the assertion to:
+ /// GetAllAsync(1, 20, null, null, null, "Bug", Arg.Any<CancellationToken>())
+ /// and remove the Skip attribute.
+ ///
+ [Fact(Skip = "Pending #116: IIssueApiClient.GetAllAsync does not yet include categoryName parameter")]
+ public async Task CategoryFilter_WhenSelected_PassesCategoryNameToApi()
+ {
+ // Arrange
+ var cut = TestContext.Render();
+
+ // Act — select "Bug" from the category filter, then click Search
+ await cut.Find("#category-filter").ChangeAsync(new ChangeEventArgs { Value = "Bug" });
+ await cut.FindAll("button").First(b => b.TextContent.Trim() == "Search")
+ .ClickAsync(new MouseEventArgs());
+
+ // Assert
+ // TODO (#116): Replace with the specific categoryName assertion once the parameter is added:
+ // _mockIssueClient.Received().GetAllAsync(1, 20, null, null, null, "Bug", Arg.Any());
+ _ = _mockIssueClient.Received()
+ .GetAllAsync(1, 20, Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any());
+ }
+
+ ///
+ /// Verifies that combining search term, status, and category all flow through
+ /// together to a single call.
+ ///
+ ///
+ /// Skipped pending issue #116: requires both the interface update (statusName/categoryName)
+ /// and the component bug fix (forwarding all filters in LoadIssues).
+ /// When #116 lands, update the assertion to:
+ /// GetAllAsync(1, 20, "crash", null, "Open", "Bug", Arg.Any<CancellationToken>())
+ /// and remove the Skip attribute.
+ ///
+ [Fact(Skip = "Pending #116: GetAllAsync missing statusName/categoryName params; LoadIssues does not forward filters")]
+ public async Task MultipleFilters_AllPassedToApiCombined()
+ {
+ // Arrange
+ var cut = TestContext.Render();
+
+ // Act — set all three filters then click Search
+ await cut.Find("#search").InputAsync(new ChangeEventArgs { Value = "crash" });
+ await cut.Find("#status-filter").ChangeAsync(new ChangeEventArgs { Value = "Open" });
+ await cut.Find("#category-filter").ChangeAsync(new ChangeEventArgs { Value = "Bug" });
+ await cut.FindAll("button").First(b => b.TextContent.Trim() == "Search")
+ .ClickAsync(new MouseEventArgs());
+
+ // Assert
+ // TODO (#116): Replace with the full assertion once the interface and component are fixed:
+ // _mockIssueClient.Received().GetAllAsync(1, 20, "crash", null, "Open", "Bug", Arg.Any());
+ _ = _mockIssueClient.Received()
+ .GetAllAsync(1, 20, Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any());
+ }
+
+ ///
+ /// Verifies that clicking Clear resets all filter fields to null and
+ /// calls with the default parameters.
+ ///
+ ///
+ /// Skipped pending issue #116: once the component forwards filters to LoadIssues,
+ /// we can assert that the post-clear call uses all-null filter args.
+ /// When #116 lands, update the assertion to verify a third call with all nulls:
+ /// GetAllAsync(1, 20, null, null, null, null, Arg.Any<CancellationToken>())
+ /// and remove the Skip attribute.
+ ///
+ [Fact(Skip = "Pending #116: LoadIssues does not yet forward filter values; full clear verification needs the fix")]
+ public async Task ClearButton_ResetsAllFiltersAndCallsApiWithDefaults()
+ {
+ // Arrange — render and set a search term
+ var cut = TestContext.Render();
+ await cut.Find("#search").InputAsync(new ChangeEventArgs { Value = "something" });
+ await cut.FindAll("button").First(b => b.TextContent.Trim() == "Search")
+ .ClickAsync(new MouseEventArgs());
+
+ // Act — click Clear
+ await cut.FindAll("button").First(b => b.TextContent.Trim() == "Clear")
+ .ClickAsync(new MouseEventArgs());
+
+ // Assert — three total calls (init, search, clear); the clear call must use page 1 and null filters
+ // TODO (#116): Once fixed, assert the third call uses all null filter params:
+ // _mockIssueClient.Received(3).GetAllAsync(Arg.Any(), ...);
+ // And use ReceivedCalls() to inspect the last call specifically.
+ _ = _mockIssueClient.Received(3)
+ .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any());
+ }
+
+ ///
+ /// Verifies that changing a filter resets the current page back to 1 before
+ /// calling .
+ ///
+ ///
+ /// Skipped pending issue #116: the Search button triggers LoadIssues(1)
+ /// directly, so pagination reset already happens structurally — but it cannot
+ /// be meaningfully verified until filter params are wired through LoadIssues.
+ /// Remove the Skip attribute and verify the page argument is 1 once #116 lands.
+ ///
+ [Fact(Skip = "Pending #116: pagination reset verification requires filter-wiring fix from #116")]
+ public async Task FilterChange_ResetsPageToOne()
+ {
+ // Arrange — render (triggers init call at page 1)
+ var cut = TestContext.Render();
+
+ // Act — change the search term and click Search (should always use page 1)
+ await cut.Find("#search").InputAsync(new ChangeEventArgs { Value = "reset-test" });
+ await cut.FindAll("button").First(b => b.TextContent.Trim() == "Search")
+ .ClickAsync(new MouseEventArgs());
+
+ // Assert — the Search call must target page 1 regardless of prior navigation
+ _ = _mockIssueClient.Received()
+ .GetAllAsync(1, 20, Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any());
+ }
+}