Files
h-school/tests/HSchool.Content.Tests/PeopleDefTests.cs
T
Leonid Pershin 5a00ad7746
ci / server (push) Failing after 3m44s
ci / client (push) Successful in 14s
Enhance school creation and staffing management with native language support
- Updated protocol documentation to include `nativeLanguages` in `nameSets` and added `nativeLanguage` to school creation options.
- Enhanced the UI for school creation to allow selection of native languages, improving user experience.
- Revised API interfaces to accommodate new native language features, ensuring proper data handling.
- Improved localization strings to support new native language functionalities in both English and Russian.
- Updated tests to validate the new native language features and ensure robust functionality in staffing scenarios.
2026-08-19 22:22:31 +03:00

331 lines
15 KiB
C#

namespace HSchool.Content.Tests;
public class PeopleDefTests
{
private readonly CatalogLoader _loader = new();
[Fact]
public void VanillaCore_LoadsPeopleDefs()
{
var root = Path.Combine(AppContext.BaseDirectory, "vanilla");
var catalog = _loader.Load([CatalogLoader.CorePackId], PackDocuments.FromDirectory(CatalogLoader.CorePackId, root));
Assert.True(catalog.Skills.Count >= 10);
Assert.Equal(10, catalog.Traits.Count);
Assert.Equal(4, catalog.Needs.Count);
Assert.True(catalog.BodyAttributes.ContainsKey("Height"));
Assert.Equal(BodyAttributeKind.Number, catalog.BodyAttributes["Height"].Kind);
Assert.Equal(BodyAttributeKind.Choice, catalog.BodyAttributes["HairColor"].Kind);
Assert.All(catalog.Needs.Values, need => Assert.True(need.DecayPerHour > 0));
Assert.True(catalog.Needs["Sleep"].RestoredOffCampus);
Assert.Equal(0.1f, catalog.Needs["Hunger"].DecayPerHour);
Assert.True(catalog.Skills["Communication"].Always);
Assert.True(catalog.Skills["Agility"].Always);
Assert.True(catalog.Skills["Pedagogy"].Work);
Assert.Equal(0.4f, catalog.Skills["English"].AdultChance);
Assert.Equal(
["RussianLanguage", "BelarusianLanguage", "UkrainianLanguage"],
catalog.NameSets["Slavic"].Spoken);
Assert.Equal(0.6f, catalog.NameSets["Slavic"].RelatedLanguageChance);
Assert.Equal(40, catalog.NameSets["Slavic"].RelatedLanguageMax);
Assert.Contains(catalog.Subjects["ForeignLanguage"].Skills, share => share.Skill == "English");
Assert.False(catalog.Skills.ContainsKey("ForeignLanguage"));
Assert.True(catalog.NameSets.ContainsKey("Slavic"));
Assert.True(catalog.NameSets["Slavic"].MaleGiven.Count >= 20);
Assert.Equal("Славянский", catalog.Label("ru", catalog.NameSets["Slavic"]));
Assert.Equal("Slavic", catalog.Label("en", catalog.NameSets["Slavic"]));
Assert.Equal("Усидчивый", catalog.Label("ru", catalog.Traits["Diligent"]));
Assert.True(catalog.Subjects.ContainsKey("PrimarySchool"));
Assert.NotNull(catalog.StaffingRules);
Assert.Equal(32, catalog.StaffingRules.PoolSize);
Assert.Equal(20, catalog.StaffingRules.BaseWeeklyHours);
Assert.Equal(4, catalog.StaffingRules.WeeksPerMonth);
Assert.Equal(36, catalog.StaffingRules.MaxWeeklyHours);
Assert.Equal("Начальные классы", catalog.Label("ru", catalog.Subjects["PrimarySchool"]));
Assert.Equal("Primary", catalog.Label("en", catalog.Subjects["PrimarySchool"]));
Assert.NotNull(catalog.DayFrame);
Assert.Equal(3, catalog.DayFrame.LongBreakAfter);
Assert.Equal("GymHall", catalog.Subjects["PhysicalEducation"].Room);
Assert.Equal("ComputerLab", catalog.Subjects["Informatics"].Room);
Assert.Null(catalog.Subjects["Mathematics"].Room);
}
[Fact]
public void Skill_InheritsRangeFromParent()
{
var catalog = _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"skills",
"base",
"""{ "defName": "AcademicBase", "abstract": true, "range": { "min": 0, "max": 100 }, "distribution": { "mean": 40, "stdDev": 10 } }"""),
PackDocuments.Def(
CatalogLoader.CorePackId,
"skills",
"math",
"""{ "defName": "Mathematics", "parent": "AcademicBase", "distribution": { "mean": 55, "stdDev": 12 } }"""),
]);
Assert.Equal(0, catalog.Skills["Mathematics"].Range.Min);
Assert.Equal(100, catalog.Skills["Mathematics"].Range.Max);
Assert.Equal(55, catalog.Skills["Mathematics"].Distribution?.Mean);
Assert.False(catalog.Skills["Mathematics"].Abstract);
Assert.True(catalog.Skills["AcademicBase"].Abstract);
}
[Fact]
public void TraitPatch_AddsAnIncompatibility()
{
var catalog = _loader.Load(
[CatalogLoader.CorePackId, "addon"],
[
PackDocuments.Def(CatalogLoader.CorePackId, "skills", "math", """{ "defName": "Mathematics" }"""),
PackDocuments.Def(
CatalogLoader.CorePackId,
"traits",
"kind",
"""{ "defName": "Kind", "weight": 1, "incompatible": [] }"""),
PackDocuments.Def(
CatalogLoader.CorePackId,
"traits",
"bully",
"""{ "defName": "Bully", "weight": 1 }"""),
PackDocuments.Patch(
"addon",
"kind",
"""{ "target": "Kind", "ops": [ { "op": "add", "path": "/incompatible/-", "value": "Bully" } ] }"""),
]);
Assert.Equal(["Bully"], catalog.Traits["Kind"].Incompatible);
Assert.Contains("Bully", catalog.TraitIncompatibilities("Kind"));
Assert.Contains("Kind", catalog.TraitIncompatibilities("Bully"));
}
[Fact]
public void UnknownBodyLimitAttribute_FailsTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"skills",
"agility",
"""{ "defName": "Agility", "bodyLimits": [ { "attribute": "Missing", "value": "X", "max": 10 } ] }"""),
]));
Assert.Contains("Missing", ex.Message);
}
/// <summary>
/// The derived build is computed from these two by name. A pack that renames them used to
/// load fine and hand every single person the fallback height and weight — and therefore the
/// same build, which then drives every body limit on every skill.
/// </summary>
[Fact]
public void BodyAttributesWithoutHeightOrWeight_FailTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"bodies",
"stature",
"""{ "defName": "Stature", "kind": "number", "distributions": [ { "distribution": { "mean": 170, "stdDev": 8 } } ] }"""),
]));
Assert.Contains("Height", ex.Message);
}
[Fact]
public void UnknownBuildValue_FailsTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"skills",
"agility",
"""{ "defName": "Agility", "bodyLimits": [ { "attribute": "Build", "value": "Huge", "max": 10 } ] }"""),
]));
Assert.Contains("Huge", ex.Message);
}
[Fact]
public void UnknownTraitIncompatibility_FailsTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"traits",
"kind",
"""{ "defName": "Kind", "incompatible": ["Ghost"] }"""),
]));
Assert.Contains("Ghost", ex.Message);
}
[Fact]
public void Subject_UnknownSkill_FailsTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(CatalogLoader.CorePackId, "skills", "math", """{ "defName": "Mathematics" }"""),
PackDocuments.Def(
CatalogLoader.CorePackId,
"subjects",
"ghost",
"""
{
"defName": "GhostSubject",
"grades": { "min": 1, "max": 4 },
"hoursPerWeek": 2,
"skills": [{ "skill": "Missing", "share": 1 }]
}
"""),
]));
Assert.Contains("Missing", ex.Message);
}
[Fact]
public void OneWayIncompatibility_IsMutual()
{
var catalog = _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(CatalogLoader.CorePackId, "traits", "a", """{ "defName": "Diligent", "weight": 1, "incompatible": ["Lazy"] }"""),
PackDocuments.Def(CatalogLoader.CorePackId, "traits", "b", """{ "defName": "Lazy", "weight": 1 }"""),
]);
Assert.Equal(["Lazy"], catalog.Traits["Diligent"].Incompatible);
Assert.Empty(catalog.Traits["Lazy"].Incompatible);
Assert.Contains("Lazy", catalog.TraitIncompatibilities("Diligent"));
Assert.Contains("Diligent", catalog.TraitIncompatibilities("Lazy"));
}
[Fact]
public void ExtraPack_AddsANameSetAndTrait()
{
var catalog = _loader.Load(
[CatalogLoader.CorePackId, "addon"],
[
PackDocuments.Def(CatalogLoader.CorePackId, "traits", "kind", """{ "defName": "Kind", "weight": 1 }"""),
PackDocuments.Def(
CatalogLoader.CorePackId,
"namesets",
"slavic",
"""
{
"defName": "Slavic",
"maleGiven": [{ "form": "Иван" }],
"femaleGiven": [{ "form": "Анна", "declension": "a" }],
"surnames": [{ "male": "Иванов", "female": "Иванова" }]
}
"""),
PackDocuments.Def("addon", "traits", "stoic", """{ "defName": "Stoic", "weight": 3 }"""),
PackDocuments.Def("addon", "traits", "cheerful", """{ "defName": "Cheerful", "weight": 2 }"""),
PackDocuments.Def(
"addon",
"namesets",
"nordic",
"""
{
"defName": "Nordic",
"maleGiven": [{ "form": "Lars", "declension": "indeclinable" }],
"femaleGiven": [{ "form": "Ingrid", "declension": "indeclinable" }],
"surnames": [{ "male": "Berg", "female": "Berg", "declension": "indeclinable" }]
}
"""),
PackDocuments.Locale("addon", "ru", """{ "Nordic": "Северный", "Stoic": "Стоик", "Cheerful": "Жизнерадостный" }"""),
]);
Assert.True(catalog.NameSets.ContainsKey("Nordic"));
Assert.True(catalog.Traits.ContainsKey("Stoic"));
Assert.True(catalog.Traits.ContainsKey("Cheerful"));
Assert.Equal("Северный", catalog.Label("ru", catalog.NameSets["Nordic"]));
Assert.Equal("Стоик", catalog.Label("ru", catalog.Traits["Stoic"]));
}
}
public class NameGrammarTests
{
[Fact]
public void HardGiven_FollowsTheConsonantModel()
{
var entry = new GivenNameEntry { Form = "Иван", Declension = NameGrammar.Hard };
Assert.Equal("Иван", NameGrammar.InflectGiven(entry, GrammaticalCase.Nominative, NameGrammar.Hard));
Assert.Equal("Ивана", NameGrammar.InflectGiven(entry, GrammaticalCase.Genitive, NameGrammar.Hard));
Assert.Equal("Ивану", NameGrammar.InflectGiven(entry, GrammaticalCase.Dative, NameGrammar.Hard));
Assert.Equal("Иваном", NameGrammar.InflectGiven(entry, GrammaticalCase.Instrumental, NameGrammar.Hard));
}
[Fact]
public void ExplicitTable_OverridesTheModel()
{
var entry = new GivenNameEntry
{
Form = "Любовь",
Declension = NameGrammar.Soft,
Cases = new CaseTable
{
Nom = "Любовь",
Gen = "Любови",
Dat = "Любови",
Acc = "Любовь",
Ins = "Любовью",
Pre = "Любови",
},
};
Assert.Equal("Любови", NameGrammar.InflectGiven(entry, GrammaticalCase.Genitive, NameGrammar.Soft));
Assert.Equal("Любовью", NameGrammar.InflectGiven(entry, GrammaticalCase.Instrumental, NameGrammar.Soft));
Assert.Equal("Любовя", NameGrammar.InflectGivenForm("Любовь", NameGrammar.Soft, GrammaticalCase.Genitive));
}
[Fact]
public void OvSurname_UsesGenderedEndings()
{
var entry = new SurnameEntry { Male = "Иванов", Female = "Иванова", Declension = NameGrammar.Ov };
Assert.Equal("Иванова", NameGrammar.InflectSurname(entry, female: false, GrammaticalCase.Genitive, NameGrammar.Ov));
Assert.Equal("Ивановой", NameGrammar.InflectSurname(entry, female: true, GrammaticalCase.Genitive, NameGrammar.Ov));
Assert.Equal("Иванову", NameGrammar.InflectSurname(entry, female: true, GrammaticalCase.Accusative, NameGrammar.Ov));
}
[Fact]
public void SlavicPatronymic_FromTheFathersName()
{
Assert.Equal("Иванович", NameGrammar.Patronymic("Иван", female: false, NameGrammar.SlavicPatronymic));
Assert.Equal("Ивановна", NameGrammar.Patronymic("Иван", female: true, NameGrammar.SlavicPatronymic));
Assert.Equal("Андреевич", NameGrammar.Patronymic("Андрей", female: false, NameGrammar.SlavicPatronymic));
Assert.Equal("Дмитриевич", NameGrammar.Patronymic("Дмитрий", female: false, NameGrammar.SlavicPatronymic));
Assert.Equal("Ильинична", NameGrammar.Patronymic("Илья", female: true, NameGrammar.SlavicPatronymic));
}
[Fact]
public void BodyBuild_ComesFromBmiBands()
{
Assert.Equal(BodyBuilds.Skinny, BodyBuilds.FromHeightAndWeight(170, 50));
Assert.Equal(BodyBuilds.Athletic, BodyBuilds.FromHeightAndWeight(170, 68));
Assert.Equal(BodyBuilds.Obese, BodyBuilds.FromHeightAndWeight(170, 90));
}
[Fact]
public void Patronymic_InflectsLikeAHardOrAStem()
{
Assert.Equal("Ивановича", NameGrammar.InflectPatronymic("Иванович", female: false, GrammaticalCase.Genitive));
Assert.Equal("Ивановны", NameGrammar.InflectPatronymic("Ивановна", female: true, GrammaticalCase.Genitive));
Assert.Equal("Ивановну", NameGrammar.InflectPatronymic("Ивановна", female: true, GrammaticalCase.Accusative));
}
}