diff --git a/binaries/data/mods/public/simulation/components/tests/test_Loot.js b/binaries/data/mods/public/simulation/components/tests/test_Loot.js
new file mode 100644
index 0000000000..9770efce80
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/tests/test_Loot.js
@@ -0,0 +1,67 @@
+Resources = {
+ "GetCodes": () => ["food", "metal", "stone", "wood"],
+ "GetTradableCodes": () => ["food", "metal", "stone", "wood"],
+ "GetBarterableCodes": () => ["food", "metal", "stone", "wood"],
+ "GetResource": () => ({}),
+ "BuildSchema": (type) => {
+ let schema = "";
+ for (let res of Resources.GetCodes())
+ schema +=
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+ return "" + schema + "";
+ }
+};
+
+Engine.LoadComponentScript("interfaces/Loot.js");
+Engine.LoadComponentScript("Loot.js");
+Engine.LoadHelperScript("ValueModification.js");
+Engine.LoadComponentScript("interfaces/ModifiersManager.js");
+
+let applyModifierOverride = (key, val, ent) => {
+ return val;
+}
+
+AddMock(SYSTEM_ENTITY, IID_ModifiersManager, {
+ "ApplyModifiers": (key, val, ent) => {
+ return applyModifierOverride(key, val, ent);
+ }
+});
+
+const cmpLoot = ConstructComponent(30, "Loot", {
+ "xp": 35,
+ "metal": 10
+});
+
+TS_ASSERT_EQUALS(cmpLoot.GetResources().xp, undefined);
+TS_ASSERT_EQUALS(cmpLoot.GetResources().metal, 10);
+TS_ASSERT_EQUALS(cmpLoot.GetResources().wood, 0);
+TS_ASSERT_UNEVAL_EQUALS(cmpLoot.GetResources(), { "food": 0, "metal": 10, "stone": 0, "wood": 0 });
+TS_ASSERT_EQUALS(cmpLoot.GetXp(), 35);
+
+const cmpLootNoXp = ConstructComponent(30, "Loot", {
+ "metal": 10,
+ "wood": 20
+});
+
+TS_ASSERT_EQUALS(cmpLootNoXp.GetResources().xp, undefined);
+TS_ASSERT_EQUALS(cmpLootNoXp.GetResources().metal, 10);
+TS_ASSERT_EQUALS(cmpLootNoXp.GetResources().wood, 20);
+TS_ASSERT_EQUALS(cmpLootNoXp.GetResources().stone, 0);
+TS_ASSERT_UNEVAL_EQUALS(cmpLootNoXp.GetResources(), { "food": 0, "metal": 10, "stone": 0, "wood": 20 });
+TS_ASSERT_EQUALS(cmpLootNoXp.GetXp(), 0);
+
+applyModifierOverride = (key, val, ent) => {
+ return key == "Loot/xp" ? 100 : val;
+}
+
+TS_ASSERT_EQUALS(cmpLootNoXp.GetXp(), 100);
+
+applyModifierOverride = (key, val, ent) => {
+ return key == "Loot/wood" ? 100 : val;
+}
+
+TS_ASSERT_EQUALS(cmpLootNoXp.GetResources().wood, 100);
diff --git a/binaries/data/mods/public/simulation/components/tests/test_Looter.js b/binaries/data/mods/public/simulation/components/tests/test_Looter.js
new file mode 100644
index 0000000000..bda2cb2052
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/tests/test_Looter.js
@@ -0,0 +1,82 @@
+Resources = {
+ "GetCodes": () => ["food", "metal", "stone", "wood"],
+ "GetTradableCodes": () => ["food", "metal", "stone", "wood"],
+ "GetBarterableCodes": () => ["food", "metal", "stone", "wood"],
+ "GetResource": () => ({}),
+ "BuildSchema": (type) => {
+ let schema = "";
+ for (let res of Resources.GetCodes())
+ schema +=
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+ return "" + schema + "";
+ }
+};
+
+Engine.LoadComponentScript("interfaces/Loot.js");
+Engine.LoadComponentScript("interfaces/Looter.js");
+Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
+Engine.LoadComponentScript("interfaces/Trader.js");
+Engine.LoadComponentScript("interfaces/ModifiersManager.js");
+Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
+Engine.LoadComponentScript("Looter.js");
+Engine.LoadComponentScript("Loot.js");
+Engine.LoadHelperScript("ValueModification.js");
+Engine.LoadHelperScript("Player.js");
+
+const looter = 30;
+const target = 42;
+const playerEntity = 10;
+const cmpLooter = ConstructComponent(looter, "Looter", {});
+cmpLooter.Collect(target);
+
+AddMock(target, IID_Loot, {
+ "GetResources": () => ({ "metal": 0, "wood": 10, "stone": 0, "food": 0 })
+});
+
+cmpLooter.Collect(target);
+
+AddMock(looter, IID_Ownership, {
+ "GetOwner": () => 1
+});
+
+AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
+ "GetPlayerByID": () => playerEntity
+});
+
+let resourceCount = {};
+AddMock(playerEntity, IID_Player, {
+ "AddResources": (amounts) => {
+ for (let type in amounts)
+ resourceCount[type] = (resourceCount[type] ?? 0) +amounts[type];
+ }
+});
+
+cmpLooter.Collect(target);
+
+TS_ASSERT_UNEVAL_EQUALS(resourceCount, { "food": 0, "metal": 0, "stone": 0, "wood": 10 });
+
+AddMock(playerEntity, IID_StatisticsTracker, {
+ "IncreaseLootCollectedCounter": (_) => {}
+});
+
+cmpLooter.Collect(target);
+
+TS_ASSERT_UNEVAL_EQUALS(resourceCount, { "food": 0, "metal": 0, "stone": 0, "wood": 20 });
+
+// Integration tests
+
+const target2 = 43;
+const cmpLootNoXp = ConstructComponent(target2, "Loot", {
+ "metal": 10,
+ "wood": 20
+});
+
+cmpLooter.Collect(target2);
+
+TS_ASSERT_UNEVAL_EQUALS(resourceCount, { "food": 0, "metal": 10, "stone": 0, "wood": 40 });
+
+