From f086e623d699436db006c4a6f9fad37f6c33efdb Mon Sep 17 00:00:00 2001 From: Josef Marek Date: Mon, 7 Sep 2026 11:26:10 +0200 Subject: [PATCH] =?UTF-8?q?Oprava:=20=C3=BAprava=20dod=C3=A1van=C3=A9=20po?= =?UTF-8?q?lo=C5=BEky=20=C4=8D=C3=ADseln=C3=ADku=20se=20ti=C5=A1e=20ztr?= =?UTF-8?q?=C3=A1cela?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nacti() uložilo dodávanou vrstvu do referenčního snímku _vychozi a tytéž objekty vložilo do kolekcí, na které se váže mřížka. Úprava buňky tak mutovala i ten snímek – UlozRozdil pak porovnával stav sám se sebou, rozdíl vyšel prázdný a oprava dodávané položky zmizela. Mazání a přidávání přitom fungovalo (tam se reference nesdílí), takže se chyba dobře schovala. Sluc nově vkládá dodávané položky jako hluboké kopie (serializací, stejně jako klonování přílohy), takže snímek zůstane netknutý. Nahlášeno na přejmenování kraje Karlovarský – přesně ten scénář teď hlídají dva regresní testy; bez opravy oba padají. Build 0 chyb / 0 varování, 57 testů zelených. Co-Authored-By: Claude Opus 5 --- .claude/agents/render-dxf.md | 2 +- .claude/agents/verifikator.md | 2 +- .claude/agents/wpf-ui.md | 2 +- CLAUDE.md | 2 +- Rozpisky.Tests/CiselnikyVrstvyTesty.cs | 43 ++++++++++++++++++++++++++ Rozpisky/Data/CiselnikyStore.cs | 15 +++++++-- 6 files changed, 60 insertions(+), 6 deletions(-) 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();