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:
@@ -51,6 +51,6 @@ Needituj produkční data: `Rozpisky/Podklady/Rozpiska.dxf`, `vzorky/schema.dxf`
|
|||||||
3. Ověř:
|
3. Ověř:
|
||||||
`dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
|
`dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
|
||||||
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q`
|
`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
|
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.
|
neprošlo, řekni to rovnou i s chybovou hláškou — nezakrývej to.
|
||||||
|
|||||||
@@ -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
|
Testy spusť i tehdy, když build projde s varováními. Když build spadne, testy nespouštěj
|
||||||
a rovnou hlas chyby buildu.
|
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:
|
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
|
spusť `Rozpisky\bin\Debug\net10.0-windows\Rozpisky.exe`, počkej ~8 s, zkontroluj, že proces
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ nebo `Rozpisky/Xlsx/` jen popiš v odpovědi a nech je na volajícím.
|
|||||||
začneš měnit UI.
|
začneš měnit UI.
|
||||||
2. `MainViewModel.cs` je velký; hledej v něm grepem, nečti ho celý zbytečně.
|
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
|
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í.
|
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ě
|
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.
|
a doporuč ruční spuštění aplikace.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q
|
|||||||
dotnet run --project "Rozpisky\Rozpisky.csproj"
|
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`).
|
(nebo použít `/overit`).
|
||||||
|
|
||||||
## Architektura — jediné pravidlo, které se nesmí porušit
|
## Architektura — jediné pravidlo, které se nesmí porušit
|
||||||
|
|||||||
@@ -178,6 +178,49 @@ public class CiselnikyVrstvyTesty
|
|||||||
Assert.Equal(new[] { "Novák", "Černý" }, vysledek.Select(j => j.Hodnota));
|
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]
|
[Fact]
|
||||||
public void KlicSeBerePodleTypuPolozky()
|
public void KlicSeBerePodleTypuPolozky()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -164,11 +164,17 @@ public static class CiselnikyStore
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>Dodávaná vrstva → odebrat skryté → přepsat/doplnit vlastními. Pořadí dodávaných
|
/// <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)
|
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 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>())
|
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)
|
private static bool StejnyObsah<T>(T a, T b)
|
||||||
=> JsonSerializer.Serialize(a, Opts) == JsonSerializer.Serialize(b, Opts);
|
=> 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)
|
private static void Replace<T>(ObservableCollection<T> target, IEnumerable<T> src)
|
||||||
{
|
{
|
||||||
target.Clear();
|
target.Clear();
|
||||||
|
|||||||
Reference in New Issue
Block a user