Wardrobes live on the person in people.json: everyday layers, textbooks per parallel, and chance-based bags from catalog data. Hauling and the carry cap are defs, not constants. The golden roster gains an items column; later family members shift because Hauling consumes extra appearance rolls. Old saves without items are dressed on load. Co-authored-by: Cursor <cursoragent@cursor.com>
218 lines
8.8 KiB
C#
218 lines
8.8 KiB
C#
namespace HSchool.People.Tests;
|
|
|
|
public class SkillGrantTests
|
|
{
|
|
[Fact]
|
|
public void FirstYear_HasCoreAndPrimary_ButNotChemistryOrEnglish()
|
|
{
|
|
var roster = Fixtures.Generate(Fixtures.Classrooms(11));
|
|
var pupils = PupilsIn(roster, 1).ToArray();
|
|
Assert.NotEmpty(pupils);
|
|
Assert.All(
|
|
pupils,
|
|
person =>
|
|
{
|
|
Assert.True(person.Skills.ContainsKey("Communication"));
|
|
Assert.True(person.Skills.ContainsKey("RussianLanguage"));
|
|
Assert.True(person.Skills.ContainsKey("Agility"));
|
|
Assert.True(person.Skills.ContainsKey("Hauling"));
|
|
Assert.True(person.Skills.ContainsKey("Mathematics"));
|
|
Assert.False(person.Skills.ContainsKey("Chemistry"));
|
|
Assert.False(person.Skills.ContainsKey("Physics"));
|
|
Assert.False(person.Skills.ContainsKey("English"));
|
|
Assert.False(person.Skills.ContainsKey("German"));
|
|
Assert.False(person.Skills.ContainsKey("Pedagogy"));
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void YearFive_HasEnglish_YearEight_HasChemistry()
|
|
{
|
|
var roster = Fixtures.Generate(Fixtures.Classrooms(11));
|
|
Assert.All(
|
|
PupilsIn(roster, 5),
|
|
person =>
|
|
{
|
|
Assert.True(person.Skills.ContainsKey("English"));
|
|
Assert.False(person.Skills.ContainsKey("Chemistry"));
|
|
Assert.False(person.Skills.ContainsKey("German"));
|
|
});
|
|
Assert.All(PupilsIn(roster, 8), person => Assert.True(person.Skills.ContainsKey("Chemistry")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Adults_HaveNativeAndCommunication_ButNotEverySkill()
|
|
{
|
|
var catalog = Fixtures.Catalog();
|
|
var roster = Fixtures.Generate(Fixtures.Classrooms(11));
|
|
var adults = roster.People.Where(person => !person.IsStudent).ToArray();
|
|
Assert.NotEmpty(adults);
|
|
|
|
var concrete = catalog.Skills.Values.Count(skill => !skill.Abstract);
|
|
Assert.All(
|
|
adults,
|
|
person =>
|
|
{
|
|
Assert.True(person.Skills.ContainsKey("Communication"));
|
|
Assert.True(person.Skills.ContainsKey("RussianLanguage"));
|
|
Assert.True(person.Skills.Count < concrete);
|
|
Assert.InRange(person.Skills.Keys.Count(name => catalog.Skills[name].Work), 0, 2);
|
|
});
|
|
Assert.Contains(
|
|
adults,
|
|
person => person.Skills.Keys.Any(name => catalog.Skills[name].Work));
|
|
}
|
|
|
|
[Fact]
|
|
public void BelarusianNative_IsGrantedToEveryone_PupilsStillStudyRussian()
|
|
{
|
|
var catalog = Fixtures.Catalog();
|
|
var roster = RosterGenerator.Generate(
|
|
catalog,
|
|
Fixtures.Classrooms(11),
|
|
Fixtures.SchoolSeed,
|
|
"Russia",
|
|
Fixtures.AsOf,
|
|
"BelarusianLanguage");
|
|
|
|
var max = catalog.Countries["Russia"].Names.RelatedLanguageMax;
|
|
Assert.All(
|
|
roster.People,
|
|
person =>
|
|
{
|
|
Assert.True(person.Skills.ContainsKey("BelarusianLanguage"));
|
|
if (person.Skills.TryGetValue("UkrainianLanguage", out var ukrainian))
|
|
{
|
|
Assert.InRange(ukrainian, 0, max);
|
|
}
|
|
});
|
|
Assert.All(
|
|
roster.People.Where(person => person.IsStudent),
|
|
person => Assert.True(person.Skills.ContainsKey("RussianLanguage")));
|
|
Assert.Contains(
|
|
roster.People.Where(person => person.IsStudent),
|
|
person => person.Skills["RussianLanguage"] > max);
|
|
}
|
|
|
|
[Fact]
|
|
public void RelatedLanguages_AreCommonAndStayLow()
|
|
{
|
|
var catalog = Fixtures.Catalog();
|
|
var max = catalog.Countries["Russia"].Names.RelatedLanguageMax;
|
|
var roster = Fixtures.Generate(Fixtures.Classrooms(11));
|
|
Assert.Contains(
|
|
roster.People,
|
|
person =>
|
|
person.Skills.ContainsKey("BelarusianLanguage")
|
|
|| person.Skills.ContainsKey("UkrainianLanguage"));
|
|
Assert.Contains(roster.People, person => !person.Skills.ContainsKey("BelarusianLanguage"));
|
|
Assert.Contains(roster.People, person => !person.Skills.ContainsKey("UkrainianLanguage"));
|
|
Assert.True(
|
|
roster.People.Count(person =>
|
|
person.Skills.ContainsKey("BelarusianLanguage")
|
|
|| person.Skills.ContainsKey("UkrainianLanguage"))
|
|
> roster.People.Count / 2);
|
|
|
|
Assert.All(
|
|
roster.People,
|
|
person =>
|
|
{
|
|
Assert.True(person.Skills.ContainsKey("RussianLanguage"));
|
|
if (person.Skills.TryGetValue("BelarusianLanguage", out var belarusian))
|
|
{
|
|
Assert.InRange(belarusian, 0, max);
|
|
}
|
|
|
|
if (person.Skills.TryGetValue("UkrainianLanguage", out var ukrainian))
|
|
{
|
|
Assert.InRange(ukrainian, 0, max);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// A school saved before native languages carries no pick, and the reload path asks for one
|
|
/// with <c>rollIfOmitted: false</c>. The contract is that it takes the first listed language
|
|
/// instead of rolling — a roll would change an existing school's tongue on the next start,
|
|
/// and the intake after it would draw different people.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ReloadWithoutASavedPick_TakesTheFirstLanguageInsteadOfRolling()
|
|
{
|
|
var names = Fixtures.Catalog().Countries["Russia"].Names;
|
|
Assert.True(names.Spoken.Count > 1, "this pins behaviour that only matters for a multi-language set");
|
|
|
|
foreach (var seed in new[] { 1, 2, 7, 20260818 })
|
|
{
|
|
Assert.Equal(names.Spoken[0], NativeLanguages.Pick(names, seed, requested: null, rollIfOmitted: false));
|
|
}
|
|
|
|
// A new school rolls instead, and the roll is stable for one seed.
|
|
var rolled = NativeLanguages.Pick(names, 7, requested: null, rollIfOmitted: true);
|
|
Assert.Contains(rolled, names.Spoken);
|
|
Assert.Equal(rolled, NativeLanguages.Pick(names, 7, requested: null, rollIfOmitted: true));
|
|
|
|
// A saved pick is honoured whichever way it is asked for.
|
|
Assert.Equal(
|
|
names.Spoken[^1],
|
|
NativeLanguages.Pick(names, 7, names.Spoken[^1], rollIfOmitted: false));
|
|
|
|
// Anything outside the set comes back null so the create path can answer 400.
|
|
Assert.Null(NativeLanguages.Pick(names, 7, "Klingon", rollIfOmitted: true));
|
|
}
|
|
|
|
[Fact]
|
|
public void OmittedNativeLanguage_IsStableForTheSameSeed()
|
|
{
|
|
var map = Fixtures.Classrooms(2);
|
|
var a = RosterGenerator.Generate(Fixtures.Catalog(), map, 7, "Russia", Fixtures.AsOf);
|
|
var b = RosterGenerator.Generate(Fixtures.Catalog(), map, 7, "Russia", Fixtures.AsOf);
|
|
Assert.Equal(
|
|
a.People.Select(person => string.Join(',', person.Skills.Keys.Order(StringComparer.Ordinal))),
|
|
b.People.Select(person => string.Join(',', person.Skills.Keys.Order(StringComparer.Ordinal))));
|
|
}
|
|
|
|
[Fact]
|
|
public void Pupils_DoNotRollWorkSkills()
|
|
{
|
|
var catalog = Fixtures.Catalog();
|
|
var roster = Fixtures.Generate(Fixtures.Classrooms(11));
|
|
Assert.All(
|
|
roster.People.Where(person => person.IsStudent),
|
|
person => Assert.DoesNotContain(person.Skills.Keys, name => catalog.Skills[name].Work));
|
|
}
|
|
|
|
[Fact]
|
|
public void Intake_YearFourGainsEnglishWithoutRerollingMath()
|
|
{
|
|
var catalog = Fixtures.Catalog();
|
|
var before = Fixtures.Generate(Fixtures.Classrooms(5));
|
|
var year4 = before.Classes.Single(schoolClass => schoolClass.Year == 4);
|
|
var pupil = before.People.First(person => person.Id == year4.PupilIds[0]);
|
|
Assert.False(pupil.Skills.ContainsKey("English"));
|
|
var math = pupil.Skills["Mathematics"];
|
|
|
|
var after = YearlyIntake.Apply(
|
|
catalog,
|
|
before,
|
|
Fixtures.SchoolSeed,
|
|
"Russia",
|
|
new DateTime(2012, 9, 1, 0, 0, 0, DateTimeKind.Utc));
|
|
var grown = after.People.First(person => person.Id == pupil.Id);
|
|
Assert.True(grown.ClassId is { } classId && after.Classes.Any(schoolClass =>
|
|
schoolClass.Id.Equals(classId, StringComparison.Ordinal) && schoolClass.Year == 5));
|
|
Assert.True(grown.Skills.ContainsKey("English"));
|
|
Assert.Equal(math, grown.Skills["Mathematics"]);
|
|
}
|
|
|
|
private static IEnumerable<Person> PupilsIn(Roster roster, int year)
|
|
{
|
|
var classIds = roster.Classes
|
|
.Where(schoolClass => schoolClass.Year == year)
|
|
.Select(schoolClass => schoolClass.Id)
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
return roster.People.Where(person =>
|
|
person.IsStudent && person.ClassId is { } classId && classIds.Contains(classId));
|
|
}
|
|
}
|