Oprava: úprava dodávané položky číselníku se tiše ztrácela

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 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 11:26:10 +02:00
co-authored by Claude Opus 5
parent cd2b82db3c
commit f086e623d6
6 changed files with 60 additions and 6 deletions
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+43
View File
@@ -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<Kraj>
{
new() { Nazev = "Jihomoravský" },
new() { Nazev = "Karlovarský" },
};
var vMrizce = new ObservableCollection<Kraj>();
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<Kraj>();
CiselnikyStore.Sluc(poRestartu, dodavana, rozdil);
Assert.Equal(new[] { "Jihomoravský", "Karlovarskýýýýý" }, poRestartu.Select(k => k.Nazev));
}
[Fact]
public void UpravaNemutujeDodavanouVrstvu()
{
var dodavana = new List<Organizace> { new() { Nazev = "SŽ Praha", Adresa = "Dlážděná 1003/7" } };
var vMrizce = new ObservableCollection<Organizace>();
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()
{
+13 -2
View File
@@ -164,11 +164,17 @@ public static class CiselnikyStore
};
/// <summary>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ě.</summary>
/// položek zůstane zachované, vlastní se řadí za ně.
/// <para>
/// Dodávané položky se do cíle vkládají jako <b>kopie</b>. Kdyby se sdílely s referenčním
/// snímkem <c>_vychozi</c>, ú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á).
/// </para></summary>
internal static void Sluc<T>(ObservableCollection<T> cil, List<T> vychozi, Vrstva<T>? uziv)
{
var skryte = new HashSet<string>(uziv?.Skryte ?? new List<string>(), 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<T>())
{
@@ -215,6 +221,11 @@ public static class CiselnikyStore
private static bool StejnyObsah<T>(T a, T b)
=> JsonSerializer.Serialize(a, Opts) == JsonSerializer.Serialize(b, Opts);
/// <summary>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.</summary>
private static T Klon<T>(T polozka)
=> JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(polozka, Opts), Opts)!;
private static void Replace<T>(ObservableCollection<T> target, IEnumerable<T> src)
{
target.Clear();