diff --git a/.claude/agents/render-dxf.md b/.claude/agents/render-dxf.md index be6e7b6..9b1454a 100644 --- a/.claude/agents/render-dxf.md +++ b/.claude/agents/render-dxf.md @@ -51,6 +51,6 @@ Needituj produkční data: `Rozpisky/Podklady/Rozpiska.dxf`, `vzorky/schema.dxf` 3. Ověř: `dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a `dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q` - (baseline je 55 zelených testů — nesmí ubýt). + (baseline je 57 zelených testů — nesmí ubýt). 4. V odpovědi vrať: co jsi změnil (soubor:řádek), proč, a výsledek buildu a testů. Když něco neprošlo, řekni to rovnou i s chybovou hláškou — nezakrývej to. diff --git a/.claude/agents/verifikator.md b/.claude/agents/verifikator.md index d4a089f..42103e6 100644 --- a/.claude/agents/verifikator.md +++ b/.claude/agents/verifikator.md @@ -18,7 +18,7 @@ dotnet test "C:\Users\marek\_Osobní\C sharp\EXE\Rozpisky\Rozpisky.Tests\Rozpis Testy spusť i tehdy, když build projde s varováními. Když build spadne, testy nespouštěj a rovnou hlas chyby buildu. -Baseline, se kterou porovnáváš: **build bez chyb a bez varování, 55 testů zelených, 0 přeskočených.** +Baseline, se kterou porovnáváš: **build bez chyb a bez varování, 57 testů zelených, 0 přeskočených.** Volitelně (jen když o to volající požádá) ověř, že aplikace naběhne: spusť `Rozpisky\bin\Debug\net10.0-windows\Rozpisky.exe`, počkej ~8 s, zkontroluj, že proces diff --git a/.claude/agents/wpf-ui.md b/.claude/agents/wpf-ui.md index b56fec4..3c5a2d9 100644 --- a/.claude/agents/wpf-ui.md +++ b/.claude/agents/wpf-ui.md @@ -36,7 +36,7 @@ nebo `Rozpisky/Xlsx/` jen popiš v odpovědi a nech je na volajícím. začneš měnit UI. 2. `MainViewModel.cs` je velký; hledej v něm grepem, nečti ho celý zbytečně. 3. Ověř: `dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a - `dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q` (baseline 55 zelených). + `dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q` (baseline 57 zelených). Build musí být **bez varování** — chyby v XAML se často projeví jen jako varování. 4. WPF chyby v bindingu build nezachytí. Pokud změna může binding rozbít, řekni to explicitně a doporuč ruční spuštění aplikace. diff --git a/CLAUDE.md b/CLAUDE.md index 67e1d74..b1daf53 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,7 @@ dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q dotnet run --project "Rozpisky\Rozpisky.csproj" ``` -Baseline: build bez chyb a varování, **55 testů zelených**. Před hlášením hotovo obojí spustit +Baseline: build bez chyb a varování, **57 testů zelených**. Před hlášením hotovo obojí spustit (nebo použít `/overit`). ## Architektura — jediné pravidlo, které se nesmí porušit diff --git a/Rozpisky.Tests/CiselnikyVrstvyTesty.cs b/Rozpisky.Tests/CiselnikyVrstvyTesty.cs index b3e9d1f..63db6e8 100644 --- a/Rozpisky.Tests/CiselnikyVrstvyTesty.cs +++ b/Rozpisky.Tests/CiselnikyVrstvyTesty.cs @@ -178,6 +178,49 @@ public class CiselnikyVrstvyTesty Assert.Equal(new[] { "Novák", "Černý" }, vysledek.Select(j => j.Hodnota)); } + [Fact] + public void UpravaDodavanePolozkyVMrizceSeZapiseDoRozdilu() + { + // Regrese: dodávané položky se dřív do mřížky vkládaly jako tytéž objekty, jaké držel + // referenční snímek dodávané vrstvy. Úprava v mřížce mutovala i snímek, rozdíl se pak + // počítal proti už změněným datům, vyšel prázdný a oprava se tiše ztratila. + // Mazání a přidávání přitom fungovalo, takže se to snadno přehlédne. + var dodavana = new List + { + new() { Nazev = "Jihomoravský" }, + new() { Nazev = "Karlovarský" }, + }; + + var vMrizce = new ObservableCollection(); + CiselnikyStore.Sluc(vMrizce, dodavana, null); + + // Uživatel přepíše buňku v mřížce. + vMrizce[1].Nazev = "Karlovarskýýýýý"; + + var rozdil = CiselnikyStore.Rozdil(vMrizce, dodavana); + + Assert.Equal(new[] { "Karlovarskýýýýý" }, rozdil.Vlastni.Select(k => k.Nazev)); + Assert.Equal(new[] { "Karlovarský" }, rozdil.Skryte); + + // A po restartu programu tam ta úprava musí být. + var poRestartu = new ObservableCollection(); + CiselnikyStore.Sluc(poRestartu, dodavana, rozdil); + Assert.Equal(new[] { "Jihomoravský", "Karlovarskýýýýý" }, poRestartu.Select(k => k.Nazev)); + } + + [Fact] + public void UpravaNemutujeDodavanouVrstvu() + { + var dodavana = new List { new() { Nazev = "SŽ Praha", Adresa = "Dlážděná 1003/7" } }; + + var vMrizce = new ObservableCollection(); + CiselnikyStore.Sluc(vMrizce, dodavana, null); + vMrizce[0].Adresa = "jiná adresa"; + + Assert.Equal("Dlážděná 1003/7", dodavana[0].Adresa); + Assert.NotSame(dodavana[0], vMrizce[0]); + } + [Fact] public void KlicSeBerePodleTypuPolozky() { diff --git a/Rozpisky/Data/CiselnikyStore.cs b/Rozpisky/Data/CiselnikyStore.cs index e74ed3b..162dd84 100644 --- a/Rozpisky/Data/CiselnikyStore.cs +++ b/Rozpisky/Data/CiselnikyStore.cs @@ -164,11 +164,17 @@ public static class CiselnikyStore }; /// Dodávaná vrstva → odebrat skryté → přepsat/doplnit vlastními. Pořadí dodávaných - /// položek zůstane zachované, vlastní se řadí za ně. + /// položek zůstane zachované, vlastní se řadí za ně. + /// + /// Dodávané položky se do cíle vkládají jako kopie. Kdyby se sdílely s referenčním + /// snímkem _vychozi, úprava v mřížce by mutovala i ten snímek – rozdíl by se pak počítal + /// proti už změněným datům, vyšel by prázdný a uživatelova oprava dodávané položky by se tiše + /// ztratila (mazání a přidávání by přitom fungovalo, což tu chybu dobře schová). + /// internal static void Sluc(ObservableCollection cil, List vychozi, Vrstva? uziv) { var skryte = new HashSet(uziv?.Skryte ?? new List(), StringComparer.OrdinalIgnoreCase); - var vysledek = vychozi.Where(x => !skryte.Contains(Klic(x!))).ToList(); + var vysledek = vychozi.Where(x => !skryte.Contains(Klic(x!))).Select(Klon).ToList(); foreach (var vlastni in uziv?.Vlastni ?? new List()) { @@ -215,6 +221,11 @@ public static class CiselnikyStore private static bool StejnyObsah(T a, T b) => JsonSerializer.Serialize(a, Opts) == JsonSerializer.Serialize(b, Opts); + /// Hluboká kopie položky (přes serializaci, stejně jako to dělá klonování přílohy). + /// Drží oddělený referenční snímek dodávané vrstvy od objektů, které edituje uživatel. + private static T Klon(T polozka) + => JsonSerializer.Deserialize(JsonSerializer.Serialize(polozka, Opts), Opts)!; + private static void Replace(ObservableCollection target, IEnumerable src) { target.Clear();