refactor: extract interfaces to Interfaces folders and consolidate filters

Move interface declarations into per-area Interfaces/ subfolders, merge the
small task-list filter classes into StatusFilter/SmartFlagFilter, and simplify
related services, converters and hub DTO handling.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-05-30 15:41:10 +02:00
parent 77100b6b3b
commit 41da124a31
42 changed files with 306 additions and 532 deletions

View File

@@ -8,7 +8,7 @@ public sealed class SmartFilterTests
[Fact]
public void MyDay_matches_my_day_tasks_regardless_of_status()
{
var f = new MyDayFilter();
var f = new SmartFlagFilter("smart:my-day", t => t.IsMyDay);
Assert.True (f.Matches(TaskFactory.Make("a", isMyDay: true, status: TaskStatus.Idle)));
Assert.True (f.Matches(TaskFactory.Make("b", isMyDay: true, status: TaskStatus.Done)));
Assert.False(f.Matches(TaskFactory.Make("c", isMyDay: false, status: TaskStatus.Idle)));
@@ -17,7 +17,7 @@ public sealed class SmartFilterTests
[Fact]
public void MyDay_count_excludes_done()
{
var f = new MyDayFilter();
var f = new SmartFlagFilter("smart:my-day", t => t.IsMyDay);
Assert.True (f.ShouldCount(TaskFactory.Make("a", isMyDay: true, status: TaskStatus.Queued)));
Assert.False(f.ShouldCount(TaskFactory.Make("b", isMyDay: true, status: TaskStatus.Done)));
Assert.False(f.ShouldCount(TaskFactory.Make("c", isMyDay: false, status: TaskStatus.Idle)));
@@ -26,7 +26,7 @@ public sealed class SmartFilterTests
[Fact]
public void Important_uses_IsStarred_with_same_split()
{
var f = new ImportantFilter();
var f = new SmartFlagFilter("smart:important", t => t.IsStarred);
Assert.True (f.Matches (TaskFactory.Make("a", isStarred: true, status: TaskStatus.Done)));
Assert.False(f.ShouldCount(TaskFactory.Make("a", isStarred: true, status: TaskStatus.Done)));
Assert.True (f.ShouldCount(TaskFactory.Make("b", isStarred: true, status: TaskStatus.Queued)));
@@ -36,7 +36,7 @@ public sealed class SmartFilterTests
[Fact]
public void Planned_uses_ScheduledFor_with_same_split()
{
var f = new PlannedFilter();
var f = new SmartFlagFilter("smart:planned", t => t.ScheduledFor != null);
var when = DateTime.Today;
Assert.True (f.Matches (TaskFactory.Make("a", scheduled: when, status: TaskStatus.Done)));
Assert.False(f.ShouldCount(TaskFactory.Make("a", scheduled: when, status: TaskStatus.Done)));

View File

@@ -8,11 +8,11 @@ public sealed class TaskListFilterRegistryTests
private readonly TaskListFilterRegistry _registry = new();
[Theory]
[InlineData("smart:my-day", typeof(MyDayFilter))]
[InlineData("smart:important", typeof(ImportantFilter))]
[InlineData("smart:planned", typeof(PlannedFilter))]
[InlineData("virtual:queued", typeof(QueuedFilter))]
[InlineData("virtual:running", typeof(RunningFilter))]
[InlineData("smart:my-day", typeof(SmartFlagFilter))]
[InlineData("smart:important", typeof(SmartFlagFilter))]
[InlineData("smart:planned", typeof(SmartFlagFilter))]
[InlineData("virtual:queued", typeof(StatusFilter))]
[InlineData("virtual:running", typeof(StatusFilter))]
[InlineData("virtual:review", typeof(ReviewFilter))]
public void Resolves_known_built_in_filters(string id, Type expected)
{

View File

@@ -11,7 +11,7 @@ public sealed class VirtualFilterTests
[Fact]
public void Queued_matches_every_queued_task_regardless_of_parent()
{
var f = new QueuedFilter();
var f = new StatusFilter("virtual:queued", TaskStatus.Queued);
Assert.True (f.Matches(TaskFactory.Make("a", status: TaskStatus.Queued)));
Assert.True (f.Matches(TaskFactory.Make("b", status: TaskStatus.Queued, parentId: "p")));
Assert.False(f.Matches(TaskFactory.Make("c", status: TaskStatus.Running)));
@@ -21,7 +21,7 @@ public sealed class VirtualFilterTests
public void Queued_count_equals_match_for_top_level_and_children_alike()
{
// The point of the consolidation: counter must agree with display set.
var f = new QueuedFilter();
var f = new StatusFilter("virtual:queued", TaskStatus.Queued);
var orphan = TaskFactory.Make("o", status: TaskStatus.Queued, parentId: "missing");
Assert.True(f.Matches(orphan));
Assert.True(f.ShouldCount(orphan));
@@ -30,7 +30,7 @@ public sealed class VirtualFilterTests
[Fact]
public void Queued_planning_parent_with_queued_kid_is_context_match()
{
var f = new QueuedFilter();
var f = new StatusFilter("virtual:queued", TaskStatus.Queued);
var parent = TaskFactory.Make("p", phase: PlanningPhase.Active, status: TaskStatus.Done);
var kid = TaskFactory.Make("k", parentId: "p", status: TaskStatus.Queued);
var all = new List<TaskEntity> { parent, kid };
@@ -42,7 +42,7 @@ public sealed class VirtualFilterTests
[Fact]
public void Queued_non_planning_parent_is_never_context_match()
{
var f = new QueuedFilter();
var f = new StatusFilter("virtual:queued", TaskStatus.Queued);
var parent = TaskFactory.Make("p", phase: PlanningPhase.None, status: TaskStatus.Done);
var kid = TaskFactory.Make("k", parentId: "p", status: TaskStatus.Queued);
var all = new List<TaskEntity> { parent, kid };
@@ -53,7 +53,7 @@ public sealed class VirtualFilterTests
[Fact]
public void Queued_planning_parent_without_queued_kid_is_not_context_match()
{
var f = new QueuedFilter();
var f = new StatusFilter("virtual:queued", TaskStatus.Queued);
var parent = TaskFactory.Make("p", phase: PlanningPhase.Active);
var kid = TaskFactory.Make("k", parentId: "p", status: TaskStatus.Done);
var all = new List<TaskEntity> { parent, kid };
@@ -66,7 +66,7 @@ public sealed class VirtualFilterTests
[Fact]
public void Running_matches_and_context_mirror_queued()
{
var f = new RunningFilter();
var f = new StatusFilter("virtual:running", TaskStatus.Running);
var parent = TaskFactory.Make("p", phase: PlanningPhase.Active);
var kid = TaskFactory.Make("k", parentId: "p", status: TaskStatus.Running);
var all = new List<TaskEntity> { parent, kid };

View File

@@ -47,7 +47,7 @@ public class GitServiceMergeTests : IDisposable
var branches = await git.ListLocalBranchesAsync(repo.RepoDir);
Assert.Contains("feature/x", branches);
Assert.True(branches.Any(b => b == "main" || b == "master"));
Assert.Contains(branches, b => b == "main" || b == "master");
}
[Fact]
@@ -117,9 +117,8 @@ public class GitServiceMergeTests : IDisposable
GitRepoFixture.RunGit(repo.RepoDir, "add", "-A");
GitRepoFixture.RunGit(repo.RepoDir, "commit", "-m", "feat: feature edit");
string defaultBranch = "main";
try { GitRepoFixture.RunGit(repo.RepoDir, "checkout", "main"); }
catch { GitRepoFixture.RunGit(repo.RepoDir, "checkout", "master"); defaultBranch = "master"; }
catch { GitRepoFixture.RunGit(repo.RepoDir, "checkout", "master"); }
File.WriteAllText(Path.Combine(repo.RepoDir, "README.md"), "# main side\n");
GitRepoFixture.RunGit(repo.RepoDir, "add", "-A");
GitRepoFixture.RunGit(repo.RepoDir, "commit", "-m", "chore: main edit");

View File

@@ -21,6 +21,7 @@ sealed class FakeWorkerClient : IWorkerClient
public int WakeQueueCalls { get; private set; }
public bool IsConnected => false;
#pragma warning disable CS0067 // events required by IWorkerClient but not exercised by this fake
public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
public event Action<string, string, DateTime>? TaskStartedEvent;
public event Action<string, string, string, DateTime>? TaskFinishedEvent;
@@ -59,6 +60,7 @@ sealed class FakeWorkerClient : IWorkerClient
public event Action<string, string, IReadOnlyList<string>>? PlanningMergeConflictEvent;
public event Action<string>? PlanningMergeAbortedEvent;
public event Action<string>? PlanningCompletedEvent;
#pragma warning restore CS0067
public Task<MergeTargetsDto?> GetMergeTargetsAsync(string taskId) => Task.FromResult<MergeTargetsDto?>(null);
public Task<IReadOnlyList<SubtaskDiffDto>> GetPlanningAggregateAsync(string planningTaskId) => Task.FromResult<IReadOnlyList<SubtaskDiffDto>>(Array.Empty<SubtaskDiffDto>());