From 1530c4701e9b7d70b08b313b37df25971f9a5f85 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 10 Sep 2026 17:57:13 +0200 Subject: [PATCH] test: cover testAdvancement --- tests/testAdvancement.cpp | 159 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/tests/testAdvancement.cpp b/tests/testAdvancement.cpp index 8c86406..6c33626 100644 --- a/tests/testAdvancement.cpp +++ b/tests/testAdvancement.cpp @@ -154,3 +154,162 @@ VF_TEST(anAdvancementIsGrantedOnlyWhenItsLastCriterionCompletes) { CHECK_EQ(progress.criterionProgress(tools, "sticks"), 2); CHECK_EQ(progress.criterionTick(tools, "sticks"), -1); + granted = progress.notify(Event::itemObtained(ItemId::Stick, 2), 140); + CHECK(granted.empty()); + CHECK(progress.criterionDone(tools, "sticks")); + CHECK_EQ(progress.criterionProgress(tools, "sticks"), 4); + CHECK_EQ(progress.criterionTick(tools, "sticks"), 140); + CHECK(!progress.granted(tools)); + + // An unrelated item moves nothing. + granted = progress.notify(Event::itemObtained(ItemId::Dirt, 64), 150); + CHECK(granted.empty()); + CHECK(!progress.granted(tools)); + + granted = progress.notify(Event::itemObtained(ItemId::CraftingTable), 200); + CHECK_EQ(granted.size(), static_cast(1)); + if (!granted.empty()) CHECK(granted[0] == tools); + CHECK(progress.granted(tools)); + CHECK_EQ(progress.grantedTick(tools), 200); + CHECK_EQ(progress.completedCriteria(tools).size(), static_cast(2)); + CHECK_EQ(progress.totalExperience(), 10); + + // Granting is a one shot: repeating the last event awards nothing again. + granted = progress.notify(Event::itemObtained(ItemId::CraftingTable), 260); + CHECK(granted.empty()); + CHECK_EQ(progress.grantedTick(tools), 200); + CHECK_EQ(progress.totalExperience(), 10); + CHECK_EQ(progress.grantedCount(), 1); +} + +VF_TEST(requirementGroupsAreSatisfiedByAnyOneOfTheirCriteria) { + std::vector errors; + AdvancementTree tree = loadTree(contentPack(), errors); + const Advancement* hunter = tree.find(id("voxelforge:hunter")); + CHECK(hunter != nullptr); + CHECK(!hunter->satisfiedBy({})); + CHECK(hunter->satisfiedBy({"creeper"})); + CHECK(hunter->satisfiedBy({"zombies"})); + CHECK(hunter->danglingRequirements().empty()); + + // Without requirement groups every criterion is needed. + const Advancement* tools = tree.find(id("voxelforge:tools")); + CHECK(tools->requirements.empty()); + CHECK(!tools->satisfiedBy({"sticks"})); + CHECK(tools->satisfiedBy({"sticks", "table"})); + + const Advancement* traveller = tree.find(id("voxelforge:traveller")); + CHECK(!traveller->satisfiedBy({"portal"})); + CHECK(!traveller->satisfiedBy({"brew", "trade", "mine"})); + CHECK(traveller->satisfiedBy({"portal", "mine"})); + + AdvancementProgress progress(tree); + std::vector granted = + progress.notify(Event::dimensionEntered(DimensionId::Nether), 10); + CHECK(granted.empty()); + granted = progress.notify(Event::dimensionEntered(DimensionId::End), 20); + CHECK(granted.empty()); + granted = progress.notify(Event::villagerTraded(VillagerProfession::Farmer, + ItemId::EmeraldGem), + 30); + CHECK(granted.empty()); + granted = progress.notify(Event::villagerTraded(VillagerProfession::Librarian, + ItemId::EmeraldGem), + 40); + CHECK_EQ(granted.size(), static_cast(1)); + if (!granted.empty()) CHECK(granted[0] == id("voxelforge:traveller")); +} + +VF_TEST(countedCriteriaNeedEveryOneOfTheirEvents) { + std::vector errors; + AdvancementTree tree = loadTree(contentPack(), errors); + AdvancementProgress progress(tree); + const AdvancementId hunter = id("voxelforge:hunter"); + + progress.notify(Event::mobKilled(Species::Zombie), 1); + progress.notify(Event::mobKilled(Species::Skeleton), 2); + CHECK_EQ(progress.criterionProgress(hunter, "zombies"), 1); + progress.notify(Event::mobKilled(Species::Zombie), 3); + CHECK_EQ(progress.criterionProgress(hunter, "zombies"), 2); + CHECK(!progress.granted(hunter)); + + std::vector granted = progress.notify(Event::mobKilled(Species::Zombie), 4); + CHECK_EQ(granted.size(), static_cast(1)); + if (!granted.empty()) CHECK(granted[0] == hunter); + CHECK(progress.criterionDone(hunter, "zombies")); + CHECK(!progress.criterionDone(hunter, "creeper")); + CHECK_EQ(progress.grantedTick(hunter), 4); + CHECK_EQ(progress.totalExperience(), 20); + CHECK_EQ(progress.awardedLootTables().size(), static_cast(1)); + CHECK(progress.awardedLootTables()[0] == id("voxelforge:chests/starter")); + CHECK(progress.unlockedRecipes().empty()); +} + +VF_TEST(hiddenAdvancementsStayOutOfSightUntilTheParentIsGranted) { + std::vector errors; + AdvancementTree tree = loadTree(contentPack(), errors); + AdvancementProgress progress(tree); + const AdvancementId hunter = id("voxelforge:hunter"); + const AdvancementId tools = id("voxelforge:tools"); + + CHECK(progress.visible(id("voxelforge:root"))); + CHECK(progress.visible(tools)); + CHECK(!progress.visible(hunter)); + + progress.notify(Event::itemObtained(ItemId::Stick, 4), 10); + CHECK(!progress.visible(hunter)); + progress.notify(Event::itemObtained(ItemId::CraftingTable), 20); + CHECK(progress.granted(tools)); + CHECK(progress.visible(hunter)); + + // A hidden advancement is also its own reveal once it is earned. + AdvancementTree lone; + Advancement secret = makeAdvancement("voxelforge:secret", ""); + secret.hidden = true; + secret.criteria[0].matchMask = AdvancementCriterion::MatchItem; + secret.criteria[0].item = ItemId::DiamondGem; + CHECK(lone.add(secret)); + AdvancementProgress hidden(lone); + CHECK(!hidden.visible(secret.id)); + hidden.notify(Event::itemObtained(ItemId::DiamondGem), 5); + CHECK(hidden.granted(secret.id)); + CHECK(hidden.visible(secret.id)); + CHECK(!hidden.visible(id("voxelforge:nobody"))); +} + +VF_TEST(aParentLoopIsReportedInsteadOfWalkedForever) { + AdvancementTree tree; + CHECK(tree.add(makeAdvancement("voxelforge:first", "voxelforge:third"))); + CHECK(tree.add(makeAdvancement("voxelforge:second", "voxelforge:first"))); + CHECK(tree.add(makeAdvancement("voxelforge:third", "voxelforge:second"))); + CHECK(tree.add(makeAdvancement("voxelforge:selfish", "voxelforge:selfish"))); + CHECK(tree.add(makeAdvancement("voxelforge:orphan", "voxelforge:nowhere"))); + + CHECK(tree.hasCycle()); + CHECK(tree.cycleFrom(id("voxelforge:first"))); + CHECK(tree.cycleFrom(id("voxelforge:second"))); + CHECK(tree.cycleFrom(id("voxelforge:selfish"))); + CHECK(!tree.cycleFrom(id("voxelforge:orphan"))); + CHECK_EQ(tree.depthOf(id("voxelforge:first")), -1); + CHECK_EQ(tree.depthOf(id("voxelforge:selfish")), -1); + CHECK_EQ(tree.depthOf(id("voxelforge:orphan")), -1); + CHECK_EQ(tree.missingParents().size(), static_cast(1)); + CHECK(tree.missingParents()[0] == id("voxelforge:orphan")); + CHECK(tree.roots().empty()); + + // A looping node still answers questions instead of hanging the caller. + AdvancementProgress progress(tree); + CHECK(progress.visible(id("voxelforge:first"))); + std::vector granted = progress.notify(Event::itemObtained(ItemId::Dirt), 1); + CHECK_EQ(granted.size(), static_cast(5)); + CHECK(progress.granted(id("voxelforge:first"))); + + // Adding the same id twice is refused, so a duplicate cannot rewrite a node. + CHECK(!tree.add(makeAdvancement("voxelforge:first", "voxelforge:third"))); + Advancement nameless; + CHECK(!tree.add(nameless)); + CHECK_EQ(tree.size(), static_cast(5)); + tree.clear(); + CHECK_EQ(tree.size(), static_cast(0)); +} +