Files
Rozpisky/Rozpisky.Tests/CiselnikyVrstvyTesty.cs
T
marekandClaude Opus 5 6cd41ee5a3 Číselníky ve dvou vrstvách: dodávaná a uživatelská
Dosud byl ciselniky.json jediný soubor vedle .exe, který si aplikace zároveň
dodávala i přepisovala. Přeinstalace novou verzí by uživateli přepsala
záznamy, které si sám nastřádal – ručně v záložce Číselníky i „samoučením“
jmen z projektů. Nově:

- dodávaná vrstva  ciselniky.vychozi.json vedle .exe, jen ke čtení; každá
  instalace ji obnoví, takže s novou verzí smí růst a měnit se,
- uživatelská vrstva  ciselniky.user.json v %APPDATA%\Rozpisky, kde je
  uložený jen rozdíl proti dodávané: Vlastni (přidané a upravené položky)
  a Skryte (klíče dodávaných položek, které si uživatel odklidil).

Slučování zachovává pořadí dodávaných položek, upravenou položku přepíše
na jejím místě a vlastní řadí za ně. Klíč se bere podle typu (Nazev /
Hodnota / Oznaceni), porovnává se bez ohledu na velikost písmen – stejně
jako to už dělá samoučení jmen. Obsah se porovnává serializací, takže
nová pole položek jsou pokrytá automaticky.

Migrace: chybí-li uživatelská vrstva a vedle .exe leží starý ciselniky.json,
vezme se jeho obsah jako aktuální stav, dopočítá se z něj rozdíl a originál
se přejmenuje na .migrovano. Nic se nemaže.

Backfill krajů v MainViewModelu tím ztratil smysl (kraje přicházejí
z dodávané vrstvy) a je odstraněný.

Slučování a dopočet rozdílu jsou zpřístupněné testům (InternalsVisibleTo) –
chyba v nich se projeví tichou ztrátou dat, takže je pokrývá 14 nových testů
včetně kolečka uložit→načíst a scénáře aktualizace programu.

Ověřeno i ručně: migrace 7,6 kB souboru na 708 B rozdílu, přežití smazání
celého bin/ a přebuildu, běh s nezapisovatelnou instalační složkou.

Build 0 chyb / 0 varování, 50 testů zelených.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 10:48:43 +02:00

192 lines
7.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.ObjectModel;
using Rozpisky.Data;
using Rozpisky.Models;
using Xunit;
namespace Rozpisky.Tests;
/// <summary>
/// Slučování dodávané a uživatelské vrstvy číselníků a dopočet rozdílu
/// (<see cref="CiselnikyStore.Sluc{T}"/> / <see cref="CiselnikyStore.Rozdil{T}"/>).
/// Na téhle logice stojí, jestli uživatel po aktualizaci programu najde svoje záznamy
/// chyba tady se projeví tichou ztrátou dat, kterou nikdo nenahlásí.
/// </summary>
public class CiselnikyVrstvyTesty
{
private static List<Jmeno> Vychozi(params string[] jmena)
=> jmena.Select(j => new Jmeno { Hodnota = j }).ToList();
private static ObservableCollection<Jmeno> Sluc(
List<Jmeno> vychozi, CiselnikyStore.Vrstva<Jmeno>? uziv)
{
var cil = new ObservableCollection<Jmeno>();
CiselnikyStore.Sluc(cil, vychozi, uziv);
return cil;
}
[Fact]
public void BezUzivatelskeVrstvyZustaneDodavanaBezeZmeny()
{
var vysledek = Sluc(Vychozi("Novák", "Svoboda"), null);
Assert.Equal(new[] { "Novák", "Svoboda" }, vysledek.Select(j => j.Hodnota));
}
[Fact]
public void VlastniPolozkySeRadiZaDodavane()
{
var uziv = new CiselnikyStore.Vrstva<Jmeno> { Vlastni = Vychozi("Dvořák") };
var vysledek = Sluc(Vychozi("Novák", "Svoboda"), uziv);
Assert.Equal(new[] { "Novák", "Svoboda", "Dvořák" }, vysledek.Select(j => j.Hodnota));
}
[Fact]
public void SkrytaDodavanaPolozkaSeNeobjevi()
{
var uziv = new CiselnikyStore.Vrstva<Jmeno> { Skryte = { "Svoboda" } };
var vysledek = Sluc(Vychozi("Novák", "Svoboda"), uziv);
Assert.Equal(new[] { "Novák" }, vysledek.Select(j => j.Hodnota));
}
[Fact]
public void UpravenaDodavanaPolozkaSePrepiseNaSvemMiste()
{
// Uživatel opravil adresu dodávané organizace musí zůstat na původní pozici,
// jinak by se mu po každé aktualizaci přeházelo pořadí v našeptávači.
var vychozi = new List<Organizace>
{
new() { Nazev = "SŽ Praha", Adresa = "stará" },
new() { Nazev = "SŽ Brno", Adresa = "Kounicova 26" },
};
var uziv = new CiselnikyStore.Vrstva<Organizace>
{
Vlastni = { new Organizace { Nazev = "SŽ Praha", Adresa = "Dlážděná 1003/7" } },
};
var cil = new ObservableCollection<Organizace>();
CiselnikyStore.Sluc(cil, vychozi, uziv);
Assert.Equal(new[] { "SŽ Praha", "SŽ Brno" }, cil.Select(o => o.Nazev));
Assert.Equal("Dlážděná 1003/7", cil[0].Adresa);
}
[Fact]
public void ParovaniIgnorujeVelikostPismen()
{
var uziv = new CiselnikyStore.Vrstva<Jmeno> { Vlastni = Vychozi("NOVÁK") };
var vysledek = Sluc(Vychozi("Novák"), uziv);
Assert.Single(vysledek);
Assert.Equal("NOVÁK", vysledek[0].Hodnota);
}
[Fact]
public void RozepsaneRadkySeNeslijouDoJednoho()
{
// Dva prázdné řádky přidané tlačítkem „Přidat záznam“ mají stejný (prázdný) klíč
// nesmí se spárovat, jinak by uživateli jeden zmizel dřív, než ho stihne vyplnit.
var uziv = new CiselnikyStore.Vrstva<Jmeno> { Vlastni = Vychozi("", "") };
var vysledek = Sluc(Vychozi("Novák"), uziv);
Assert.Equal(3, vysledek.Count);
}
[Fact]
public void RozdilZachytiPridaniUpravuISkryti()
{
var vychozi = Vychozi("Novák", "Svoboda", "Černý");
// Uživatel: Svobodu smazal, přidal Dvořáka, Novák zůstal beze změny.
var stav = Vychozi("Novák", "Černý", "Dvořák");
var rozdil = CiselnikyStore.Rozdil(stav, vychozi);
Assert.Equal(new[] { "Dvořák" }, rozdil.Vlastni.Select(j => j.Hodnota));
Assert.Equal(new[] { "Svoboda" }, rozdil.Skryte);
}
[Fact]
public void RozdilNepovazujeNezmenenouPolozkuZaVlastni()
{
var vychozi = Vychozi("Novák", "Svoboda");
var rozdil = CiselnikyStore.Rozdil(Vychozi("Novák", "Svoboda"), vychozi);
Assert.Empty(rozdil.Vlastni);
Assert.Empty(rozdil.Skryte);
}
[Fact]
public void RozdilZachytiZmenuJinehoNezKlicovehoPole()
{
var vychozi = new List<Firma>
{
new() { Nazev = "EXprojekt s.r.o.", Telefon = "+420 533 312 000" },
};
var stav = new List<Firma>
{
new() { Nazev = "EXprojekt s.r.o.", Telefon = "+420 111 222 333" },
};
var rozdil = CiselnikyStore.Rozdil(stav, vychozi);
Assert.Single(rozdil.Vlastni);
Assert.Equal("+420 111 222 333", rozdil.Vlastni[0].Telefon);
Assert.Empty(rozdil.Skryte); // klíč zůstal, nejde o skrytí
}
[Fact]
public void PrejmenovaniDodavanePolozkySeUloziJakoSkrytiAPridani()
{
var vychozi = Vychozi("Nowak");
var rozdil = CiselnikyStore.Rozdil(Vychozi("Novák"), vychozi);
Assert.Equal(new[] { "Novák" }, rozdil.Vlastni.Select(j => j.Hodnota));
Assert.Equal(new[] { "Nowak" }, rozdil.Skryte);
}
[Fact]
public void RozdilASlouceniDavajiZpetPuvodniStav()
{
// Kolečko uložit → načíst nesmí nic ztratit ani přidat, ať se s číselníkem stalo cokoli.
var vychozi = Vychozi("Novák", "Svoboda", "Černý");
var stav = Vychozi("Novák", "Černý", "Dvořák", "Horák");
var vysledek = Sluc(vychozi, CiselnikyStore.Rozdil(stav, vychozi));
Assert.Equal(stav.Select(j => j.Hodnota), vysledek.Select(j => j.Hodnota));
}
[Fact]
public void NovaDodavanaPolozkaSePoAktualizaciObjeviAVlastniZustanou()
{
// Jádro celé změny: program vydá novou verzi s doplněným číselníkem, uživatelův
// rozdíl je pořád ten starý nová položka musí přibýt, vlastní nesmí zmizet.
var stareVychozi = Vychozi("Novák");
var rozdilUzivatele = CiselnikyStore.Rozdil(Vychozi("Novák", "Dvořák"), stareVychozi);
var noveVychozi = Vychozi("Novák", "Svoboda");
var vysledek = Sluc(noveVychozi, rozdilUzivatele);
Assert.Equal(new[] { "Novák", "Svoboda", "Dvořák" }, vysledek.Select(j => j.Hodnota));
}
[Fact]
public void SkrytiPrezijeAktualizaciDodavaneVrstvy()
{
var stareVychozi = Vychozi("Novák", "Svoboda");
var rozdilUzivatele = CiselnikyStore.Rozdil(Vychozi("Novák"), stareVychozi);
// Nová verze programu Svobodu pořád dodává uživatel ho ale jednou odklidil.
var vysledek = Sluc(Vychozi("Novák", "Svoboda", "Černý"), rozdilUzivatele);
Assert.Equal(new[] { "Novák", "Černý" }, vysledek.Select(j => j.Hodnota));
}
[Fact]
public void KlicSeBerePodleTypuPolozky()
{
Assert.Equal("SŽ", CiselnikyStore.Klic(new Organizace { Nazev = "SŽ" }));
Assert.Equal("EXprojekt", CiselnikyStore.Klic(new Firma { Nazev = "EXprojekt" }));
Assert.Equal("Novák", CiselnikyStore.Klic(new Jmeno { Hodnota = "Novák" }));
Assert.Equal("DPS", CiselnikyStore.Klic(new VerzeDokumentace { Hodnota = "DPS" }));
Assert.Equal("D", CiselnikyStore.Klic(new Cast { Oznaceni = "D", Nazev = "Mosty" }));
Assert.Equal("Jihomoravský", CiselnikyStore.Klic(new Kraj { Nazev = "Jihomoravský" }));
}
}