feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
using ClawdDotNet.Core.Tasks;
|
||||
using Shouldly;
|
||||
|
||||
namespace ClawdDotNet.Core.Tests.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Der Frontmatter-Parser ist die Brücke zwischen der menschen-/agentenlesbaren
|
||||
/// Task-Datei und dem DB-Zustand. Was er falsch liest, importiert das Board falsch —
|
||||
/// deshalb steht hier der Rundlauf im Mittelpunkt: Serialisieren und wieder Einlesen
|
||||
/// muss dieselbe Definition ergeben.
|
||||
/// </summary>
|
||||
public sealed class TaskFrontmatterTests
|
||||
{
|
||||
[Fact]
|
||||
public void Ein_vollstaendiges_Frontmatter_wird_gelesen()
|
||||
{
|
||||
var text = """
|
||||
---
|
||||
id: t-8f3a2c
|
||||
title: NVDA Earnings recherchieren
|
||||
status: todo
|
||||
type: work
|
||||
priority: 4
|
||||
assignee: "@crawler"
|
||||
when:
|
||||
kind: cron
|
||||
value: "0 7 * * 1-5"
|
||||
tz: Europe/Berlin
|
||||
require_approval: true
|
||||
acceptance: |
|
||||
Aktuelle Zahlen mit Datum und Quelle.
|
||||
In SharedWorkspace/data/nvda.json abgelegt.
|
||||
blocked_by: [t-4b1e, t-9a2c]
|
||||
onlyWhenMarketOpen: false
|
||||
---
|
||||
|
||||
Recherchiere die neuesten Quartalszahlen.
|
||||
""";
|
||||
|
||||
TaskFrontmatter.TryParse(text, out var task, out var error).ShouldBeTrue(error);
|
||||
|
||||
task.Id.ShouldBe("t-8f3a2c");
|
||||
task.Title.ShouldBe("NVDA Earnings recherchieren");
|
||||
task.Status.ShouldBe(TaskItemStatus.Todo);
|
||||
task.Type.ShouldBe(TaskItemType.Work);
|
||||
task.Priority.ShouldBe(4);
|
||||
task.Assignee.ShouldBe("@crawler");
|
||||
task.When.ShouldNotBeNull();
|
||||
task.When!.Kind.ShouldBe(TaskWhenKind.Cron);
|
||||
task.When.Value.ShouldBe("0 7 * * 1-5");
|
||||
task.When.TimeZone.ShouldBe("Europe/Berlin");
|
||||
task.RequireApproval.ShouldBeTrue();
|
||||
task.Acceptance.ShouldContain("nvda.json");
|
||||
task.Acceptance.ShouldContain("Aktuelle Zahlen");
|
||||
task.BlockedBy.ShouldBe(["t-4b1e", "t-9a2c"]);
|
||||
task.OnlyWhenMarketOpen.ShouldBeFalse();
|
||||
task.Body.ShouldBe("Recherchiere die neuesten Quartalszahlen.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialisieren_und_wieder_Einlesen_ergibt_dieselbe_Definition()
|
||||
{
|
||||
var original = new TaskItem
|
||||
{
|
||||
Id = "t-abc123",
|
||||
Title = "Titel mit: Doppelpunkt und # Raute",
|
||||
Status = TaskItemStatus.InReview,
|
||||
Type = TaskItemType.Approval,
|
||||
Priority = 5,
|
||||
Assignee = "@human",
|
||||
When = new TaskWhen { Kind = TaskWhenKind.Every, Value = "30m", TimeZone = "UTC" },
|
||||
RequireApproval = true,
|
||||
Acceptance = "Zeile eins\nZeile zwei",
|
||||
BlockedBy = ["t-1", "t-2"],
|
||||
OnlyWhenMarketOpen = true,
|
||||
Body = "Mehrzeiliger\nRumpf mit Umlauten äöü und 🦀."
|
||||
};
|
||||
|
||||
var text = TaskFrontmatter.Serialize(original);
|
||||
TaskFrontmatter.TryParse(text, out var round, out var error).ShouldBeTrue(error);
|
||||
|
||||
round.Id.ShouldBe(original.Id);
|
||||
round.Title.ShouldBe(original.Title);
|
||||
round.Status.ShouldBe(original.Status);
|
||||
round.Type.ShouldBe(original.Type);
|
||||
round.Priority.ShouldBe(original.Priority);
|
||||
round.Assignee.ShouldBe(original.Assignee);
|
||||
round.When!.Kind.ShouldBe(TaskWhenKind.Every);
|
||||
round.When.Value.ShouldBe("30m");
|
||||
round.When.TimeZone.ShouldBe("UTC");
|
||||
round.RequireApproval.ShouldBeTrue();
|
||||
round.Acceptance.ShouldBe(original.Acceptance);
|
||||
round.BlockedBy.ShouldBe(["t-1", "t-2"]);
|
||||
round.OnlyWhenMarketOpen.ShouldBeTrue();
|
||||
round.Body.ShouldBe(original.Body);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ein_Assignee_mit_At_ueberlebt_den_Rundlauf()
|
||||
{
|
||||
// In YAML ist ein führendes '@' reserviert — ohne Quotierung ginge es verloren.
|
||||
var task = new TaskItem { Id = "t-1", Title = "x", Assignee = "@new" };
|
||||
|
||||
var text = TaskFrontmatter.Serialize(task);
|
||||
TaskFrontmatter.TryParse(text, out var round, out _).ShouldBeTrue();
|
||||
|
||||
round.Assignee.ShouldBe("@new");
|
||||
TaskAssignee.KindOf(round.Assignee).ShouldBe(TaskAssigneeKind.New);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ohne_when_ist_es_eine_einmalige_Aufgabe()
|
||||
{
|
||||
var text = """
|
||||
---
|
||||
id: t-1
|
||||
title: Einmalig
|
||||
assignee: "@new"
|
||||
---
|
||||
Mach das einmal.
|
||||
""";
|
||||
|
||||
TaskFrontmatter.TryParse(text, out var task, out _).ShouldBeTrue();
|
||||
task.When.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fehlende_Felder_fallen_auf_sichere_Standards_zurueck()
|
||||
{
|
||||
var text = """
|
||||
---
|
||||
id: t-1
|
||||
title: Minimal
|
||||
---
|
||||
""";
|
||||
|
||||
TaskFrontmatter.TryParse(text, out var task, out _).ShouldBeTrue();
|
||||
|
||||
task.Status.ShouldBe(TaskItemStatus.Todo);
|
||||
task.Type.ShouldBe(TaskItemType.Work);
|
||||
task.Priority.ShouldBe(3);
|
||||
task.Assignee.ShouldBe(TaskAssignee.Human, "ohne Zuordnung wartet die Aufgabe auf einen Menschen");
|
||||
task.RequireApproval.ShouldBeFalse();
|
||||
task.BlockedBy.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Eine_unbekannte_Terminart_wird_verworfen_statt_geraten()
|
||||
{
|
||||
var text = """
|
||||
---
|
||||
id: t-1
|
||||
title: x
|
||||
when:
|
||||
kind: phantasie
|
||||
value: irgendwas
|
||||
tz: UTC
|
||||
---
|
||||
""";
|
||||
|
||||
TaskFrontmatter.TryParse(text, out var task, out _).ShouldBeTrue();
|
||||
task.When.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fehlt_der_Frontmatter_Block_meldet_der_Parser_das()
|
||||
{
|
||||
TaskFrontmatter.TryParse("Nur Text, kein Frontmatter.", out _, out var error).ShouldBeFalse();
|
||||
error.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ein_nicht_geschlossener_Block_wird_gemeldet()
|
||||
{
|
||||
var text = "---\nid: t-1\ntitle: x\n";
|
||||
TaskFrontmatter.TryParse(text, out _, out var error).ShouldBeFalse();
|
||||
error.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Windows_Zeilenenden_stoeren_nicht()
|
||||
{
|
||||
var text = "---\r\nid: t-1\r\ntitle: Mit CRLF\r\n---\r\nRumpf\r\n";
|
||||
TaskFrontmatter.TryParse(text, out var task, out var error).ShouldBeTrue(error);
|
||||
task.Title.ShouldBe("Mit CRLF");
|
||||
task.Body.ShouldBe("Rumpf");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user