Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/GarrisonHolder.js	(revision 18599)
+++ binaries/data/mods/public/simulation/components/GarrisonHolder.js	(working copy)
@@ -65,16 +65,15 @@
 	this.allowGarrisoning = {};
 	this.visibleGarrisonPoints = [];
 	if (this.template.VisibleGarrisonPoints)
-	{
-		for each (var offset in this.template.VisibleGarrisonPoints)
-		{
-			var o = {};
-			o.x = +offset.X;
-			o.y = +offset.Y;
-			o.z = +offset.Z;
-			this.visibleGarrisonPoints.push({"offset":o, "entity": null});
-		}
-	}
+		for (let name in this.template.VisibleGarrisonPoints)
+			this.visibleGarrisonPoints.push({
+				"offset": {
+					"x": +this.template.VisibleGarrisonPoints[name].X,
+					"y": +this.template.VisibleGarrisonPoints[name].Y,
+					"z": +this.template.VisibleGarrisonPoints[name].Z
+				},
+				"entity": null
+			});
 };
 
 /**
@@ -82,8 +81,7 @@
  */
 GarrisonHolder.prototype.GetLoadingRange = function()
 {
-	var max = +this.template.LoadingRange;
-	return { "max": max, "min": 0 };
+	return { "max": +this.template.LoadingRange, "min": 0 };
 };
 
 /**
@@ -93,11 +91,10 @@
 {
 	if (!this.template.Pickup || this.IsFull())
 		return false;
-	var cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership);
+	let cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership);
 	if (!cmpOwner)
 		return false;
-	var player = cmpOwner.GetOwner();
-	return IsOwnedByPlayer(player, ent);
+	return IsOwnedByPlayer(cmpOwner.GetOwner(), ent);
 };
 
 
@@ -163,11 +160,10 @@
  */
 GarrisonHolder.prototype.IsGarrisoningAllowed = function()
 {
-	for each (var allow in this.allowGarrisoning)
-	{
-		if (!allow)
+	for (let allow in this.allowGarrisoning)
+		if (!this.allowGarrisoning[allow])
 			return false;
-	}
+
 	return true;
 };
 
@@ -176,11 +172,11 @@
  */
 GarrisonHolder.prototype.GetGarrisonedEntitiesCount = function()
 {
-	var count = 0;
-	for each (var ent in this.entities)
+	let count = 0;
+	for (let ent of this.entities)
 	{
-		count++;
-		var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
+		++count;
+		let cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
 		if (cmpGarrisonHolder)
 			count += cmpGarrisonHolder.GetGarrisonedEntitiesCount();
 	}
@@ -196,11 +192,10 @@
 	if (!this.IsGarrisoningAllowed())
 		return false;
 
-	var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
+	let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
 	if (!cmpIdentity)
 		return false;
-	var entityClasses = cmpIdentity.GetClassesList();
-	return MatchesClassList(entityClasses, this.template.List._string);
+	return MatchesClassList(cmpIdentity.GetClassesList(), this.template.List._string);
 };
 
 /**
@@ -211,7 +206,7 @@
  */
 GarrisonHolder.prototype.Garrison = function(entity, vgpEntity)
 {
-	var cmpPosition = Engine.QueryInterface(entity, IID_Position);
+	let cmpPosition = Engine.QueryInterface(entity, IID_Position);
 	if (!cmpPosition)
 		return false;
 
@@ -250,12 +245,12 @@
 		return false;
 
 	// Check if the unit is allowed to be garrisoned inside the building
-	if(!this.AllowedToGarrison(entity))
+	if (!this.AllowedToGarrison(entity))
 		return false;
 
 	// check the capacity
-	var extraCount = 0;
-	var cmpGarrisonHolder = Engine.QueryInterface(entity, IID_GarrisonHolder);
+	let extraCount = 0;
+	let cmpGarrisonHolder = Engine.QueryInterface(entity, IID_GarrisonHolder);
 	if (cmpGarrisonHolder)
 		extraCount += cmpGarrisonHolder.GetGarrisonedEntitiesCount();
 	if (this.GetGarrisonedEntitiesCount() + extraCount >= this.GetCapacity())
@@ -263,26 +258,27 @@
 
 	if (!this.timer && this.GetHealRate() > 0)
 	{
- 		var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
-		this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
+		let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+		if (cmpTimer)
+			this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
 	}
 
 	// Actual garrisoning happens here
 	this.entities.push(entity);
 	this.UpdateGarrisonFlag();
-	var cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue);
+	let cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue);
 	if (cmpProductionQueue)
 		cmpProductionQueue.PauseProduction();
 
-	var cmpAura = Engine.QueryInterface(entity, IID_Auras);
+	let cmpAura = Engine.QueryInterface(entity, IID_Auras);
 	if (cmpAura && cmpAura.HasGarrisonAura())
 		cmpAura.ApplyGarrisonBonus(this.entity);
 
-	Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added" : [entity], "removed": [] });
+	Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [entity], "removed": [] });
 
-	var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
+	let cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
 	if (cmpUnitAI && cmpUnitAI.IsUnderAlert())
-		Engine.PostMessage(cmpUnitAI.GetAlertRaiser(), MT_UnitGarrisonedAfterAlert, {"holder": this.entity, "unit": entity});
+		Engine.PostMessage(cmpUnitAI.GetAlertRaiser(), MT_UnitGarrisonedAfterAlert, { "holder": this.entity, "unit": entity });
 
 	return true;
 };
@@ -294,45 +290,41 @@
  */
 GarrisonHolder.prototype.Eject = function(entity, forced)
 {
-	var entityIndex = this.entities.indexOf(entity);
+	let entityIndex = this.entities.indexOf(entity);
 	// Error: invalid entity ID, usually it's already been ejected
 	if (entityIndex == -1)
 		return false; // Fail
 
 	// Find spawning location
-	var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
-	var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
-	var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
+	let cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
+	let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
+	let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
 	// If the garrisonHolder is a sinking ship, restrict the location to the intersection of both passabilities
 	// TODO: should use passability classes to be more generic
-	if ((!cmpHealth || cmpHealth.GetHitpoints() == 0) && cmpIdentity && cmpIdentity.HasClass("Ship"))
-		var pos = cmpFootprint.PickSpawnPointBothPass(entity);
-	else
-		var pos = cmpFootprint.PickSpawnPoint(entity);
+	let pos = (!cmpHealth || cmpHealth.GetHitpoints() === 0) && cmpIdentity && cmpIdentity.HasClass("Ship") ?
+		cmpFootprint.PickSpawnPointBothPass(entity) :
+		cmpFootprint.PickSpawnPoint(entity);
 
 	if (pos.y < 0)
 	{
 		// Error: couldn't find space satisfying the unit's passability criteria
-		if (forced)
-		{	// If ejection is forced, we need to continue, so use center of the building
-			var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+		if (!forced)
+			return false;
+		// If ejection is forced, we need to continue, so use center of the building
+		let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+		if (cmpPosition)
 			pos = cmpPosition.GetPosition();
-		}
-		else
-		{	// Fail
-			return false;
-		}
 	}
 
-	var cmpNewPosition = Engine.QueryInterface(entity, IID_Position);
+	let cmpNewPosition = Engine.QueryInterface(entity, IID_Position);
 	this.entities.splice(entityIndex, 1);
 
-	for (var vgp of this.visibleGarrisonPoints)
+	let cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
+	for (let vgp of this.visibleGarrisonPoints)
 	{
 		if (vgp.entity != entity)
 			continue;
 		cmpNewPosition.SetTurretParent(INVALID_ENTITY, new Vector3D());
-		var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
 		if (cmpUnitAI)
 			cmpUnitAI.ResetTurretStance();
 		vgp.entity = null;
@@ -339,15 +331,14 @@
 		break;
 	}
 
-	var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
 	if (cmpUnitAI)
 		cmpUnitAI.Ungarrison();
 
-	var cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue);
+	let cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue);
 	if (cmpProductionQueue)
 		cmpProductionQueue.UnpauseProduction();
 
-	var cmpAura = Engine.QueryInterface(entity, IID_Auras);
+	let cmpAura = Engine.QueryInterface(entity, IID_Auras);
 	if (cmpAura && cmpAura.HasGarrisonAura())
 		cmpAura.RemoveGarrisonBonus(this.entity);
 
@@ -356,7 +347,7 @@
 	cmpNewPosition.SetHeightOffset(0);
 	// TODO: what direction should they face in?
 
-	Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added" : [], "removed": [entity] });
+	Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [], "removed": [entity] });
 
 	return true;
 };
@@ -366,21 +357,19 @@
  */
 GarrisonHolder.prototype.OrderWalkToRallyPoint = function(entities)
 {
-	var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
-	var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
+	let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+	let cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
 	if (cmpRallyPoint)
 	{
-		var rallyPos = cmpRallyPoint.GetPositions()[0];
+		let rallyPos = cmpRallyPoint.GetPositions()[0];
 		if (rallyPos)
 		{
-			var commands = GetRallyPointCommands(cmpRallyPoint, entities);
+			let commands = GetRallyPointCommands(cmpRallyPoint, entities);
 			// ignore the rally point if it is autogarrison
 			if (commands[0].type == "garrison" && commands[0].target == this.entity)
 				return;
-			for each (var com in commands)
-			{
-				ProcessCommand(cmpOwnership.GetOwner(), com);
-			}
+			for (let command of commands)
+				ProcessCommand(cmpOwnership.GetOwner(), command);
 		}
 	}
 };
@@ -396,15 +385,15 @@
 	if (!this.IsGarrisoningAllowed() && !forced)
 		return false;
 
-	var ejectedEntities = [];
-	var success = true;
-	var failedRadius;
-	for (var entity of entities)
+	let ejectedEntities = [];
+	let success = true;
+	let failedRadius;
+	for (let entity of entities)
 	{
 		if (failedRadius !== undefined)
 		{
-			var cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
-			var radius = cmpObstruction ? cmpObstruction.GetUnitRadius() : 0;
+			let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
+			let radius = cmpObstruction ? cmpObstruction.GetUnitRadius() : 0;
 			if (radius >= failedRadius)
 				continue;
 		}
@@ -411,8 +400,8 @@
 
 		if (this.Eject(entity, forced))
 		{
-			var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
-			var cmpEntOwnership = Engine.QueryInterface(entity, IID_Ownership);
+			let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+			let cmpEntOwnership = Engine.QueryInterface(entity, IID_Ownership);
 			if (cmpOwnership && cmpEntOwnership && cmpOwnership.GetOwner() == cmpEntOwnership.GetOwner())
 				ejectedEntities.push(entity);
 		}
@@ -423,7 +412,7 @@
 				failedRadius = Math.min(failedRadius, radius);
 			else
 			{
-				var cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
+				let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
 				failedRadius = cmpObstruction ? cmpObstruction.GetUnitRadius() : 0;
 			}
 		}
@@ -454,22 +443,21 @@
  */
 GarrisonHolder.prototype.UnloadTemplate = function(extendedTemplate, all, forced)
 {
-	var index = extendedTemplate.indexOf("&");
+	let index = extendedTemplate.indexOf("&");
 	if (index == -1)
 		return false;
 
-	var owner = +extendedTemplate.slice(1,index);
-	var template = extendedTemplate.slice(index+1);
+	let owner = +extendedTemplate.slice(1,index);
+	let template = extendedTemplate.slice(index+1);
 
-	var entities = [];
-	var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
-	for each (var entity in this.entities)
+	let entities = [];
+	let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+	for (let entity of this.entities)
 	{
-		var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
+		let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
 
 		// Units with multiple ranks are grouped together.
-		var name = cmpIdentity.GetSelectionGroupName()
-		           || cmpTemplateManager.GetCurrentTemplateName(entity);
+		let name = cmpIdentity.GetSelectionGroupName() || cmpTemplateManager.GetCurrentTemplateName(entity);
 
 		if (name != template)
 			continue;
@@ -493,8 +481,8 @@
  */
 GarrisonHolder.prototype.UnloadAllByOwner = function(owner, forced)
 {
-	var entities = this.entities.filter(ent => {
-		var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
+	let entities = this.entities.filter(ent => {
+		let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
 		return cmpOwnership && cmpOwnership.GetOwner() == owner;
 	});
 	return this.PerformEject(entities, forced);
@@ -507,8 +495,7 @@
  */
 GarrisonHolder.prototype.UnloadAll = function(forced)
 {
-	var entities = this.entities.slice();
-	return this.PerformEject(entities, forced);
+	return this.PerformEject(this.entities.slice(), forced);
 };
 
 /**
@@ -518,10 +505,7 @@
 GarrisonHolder.prototype.OnHealthChanged = function(msg)
 {
 	if (!this.HasEnoughHealth() && this.entities.length)
-	{
-		var entities = this.entities.slice();
-		this.EjectOrKill(entities);
-	}
+		this.EjectOrKill(this.entities.slice());
 };
 
 /**
@@ -529,10 +513,10 @@
  */
 GarrisonHolder.prototype.HasEnoughHealth = function()
 {
-	var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
-	var hitpoints = cmpHealth.GetHitpoints();
-	var maxHitpoints = cmpHealth.GetMaxHitpoints();
-	var ejectHitpoints = Math.floor((+this.template.EjectHealth) * maxHitpoints);
+	let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
+	let hitpoints = cmpHealth.GetHitpoints();
+	let maxHitpoints = cmpHealth.GetMaxHitpoints();
+	let ejectHitpoints = Math.floor((+this.template.EjectHealth) * maxHitpoints);
 	return hitpoints > ejectHitpoints;
 };
 
@@ -541,27 +525,25 @@
  */
 GarrisonHolder.prototype.HealTimeout = function(data)
 {
-	if (this.entities.length == 0)
+	let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+	if (!this.entities.length)
 	{
-		var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
 		cmpTimer.CancelTimer(this.timer);
 		this.timer = undefined;
+		return;
 	}
-	else
+
+	for (let entity of this.entities)
 	{
-		for each (var entity in this.entities)
+		let cmpHealth = Engine.QueryInterface(entity, IID_Health);
+		if (cmpHealth)
 		{
-			var cmpHealth = Engine.QueryInterface(entity, IID_Health);
-			if (cmpHealth)
-			{
-				// We do not want to heal unhealable units
-				if (!cmpHealth.IsUnhealable())
-					cmpHealth.Increase(this.GetHealRate());
-			}
+			// We do not want to heal unhealable units
+			if (!cmpHealth.IsUnhealable())
+				cmpHealth.Increase(this.GetHealRate());
 		}
-		var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
-		this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
 	}
+	this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
 };
 
 GarrisonHolder.prototype.UpdateGarrisonFlag = function()
@@ -580,8 +562,9 @@
 {
 	if (this.timer)
 	{
-		var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
-		cmpTimer.CancelTimer(this.timer);
+		let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+		if (cmpTimer)
+			cmpTimer.CancelTimer(this.timer);
 	}
 };
 
@@ -595,8 +578,8 @@
 	// the ownership change may be on the garrisonholder
 	if (this.entity == msg.entity)
 	{
-		var entities = [];
-		for each (var entity in this.entities)
+		let entities = [];
+		for (let entity of this.entities)
 		{
 			if (msg.to == -1 || !IsOwnedByMutualAllyOfEntity(this.entity, entity))
 				entities.push(entity);
@@ -607,18 +590,18 @@
 	}
 
 	// or on some of its garrisoned units
-	var entityIndex = this.entities.indexOf(msg.entity);
+	let entityIndex = this.entities.indexOf(msg.entity);
 	if (entityIndex != -1)
 	{
 		// If the entity is dead, remove it directly instead of ejecting the corpse
-		var cmpHealth = Engine.QueryInterface(msg.entity, IID_Health);
-		if (cmpHealth && cmpHealth.GetHitpoints() == 0)
+		let cmpHealth = Engine.QueryInterface(msg.entity, IID_Health);
+		if (cmpHealth && cmpHealth.GetHitpoints() === 0)
 		{
 			this.entities.splice(entityIndex, 1);
-			Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added" : [], "removed": [msg.entity] });
+			Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [], "removed": [msg.entity] });
 			this.UpdateGarrisonFlag();
 
-			for (var pt of this.visibleGarrisonPoints)
+			for (let pt of this.visibleGarrisonPoints)
 				if (pt.entity == msg.entity)
 					pt.entity = null;
 		}
@@ -632,7 +615,7 @@
  */
 GarrisonHolder.prototype.OnGlobalEntityRenamed = function(msg)
 {
-	var entityIndex = this.entities.indexOf(msg.entity);
+	let entityIndex = this.entities.indexOf(msg.entity);
 	if (entityIndex != -1)
 	{
 		let vgpRenamed;
@@ -671,7 +654,7 @@
  */
 GarrisonHolder.prototype.OnDiplomacyChanged = function()
 {
-	var entities = this.entities.filter(ent => !IsOwnedByMutualAllyOfEntity(this.entity, ent));
+	let entities = this.entities.filter(ent => !IsOwnedByMutualAllyOfEntity(this.entity, ent));
 	this.EjectOrKill(entities);
 };
 
@@ -686,20 +669,19 @@
 	// is inside a holder which kills its entities, so do not eject)
 	if (cmpPosition.IsInWorld())
 	{
-		var cmpGarrisonHolder = this;
-		var ejectables = entities.filter(function(ent) { return cmpGarrisonHolder.IsEjectable(ent); });
+		let ejectables = entities.filter(ent => this.IsEjectable(ent), this);
 		if (ejectables.length)
 			this.PerformEject(ejectables, false);
 	}
 
 	// And destroy all remaining entities
-	var killedEntities = [];
-	for each (var entity in entities)
+	let killedEntities = [];
+	for (let entity of entities)
 	{
-		var entityIndex = this.entities.indexOf(entity);
+		let entityIndex = this.entities.indexOf(entity);
 		if (entityIndex == -1)
 			continue;
-		var cmpHealth = Engine.QueryInterface(entity, IID_Health);
+		let cmpHealth = Engine.QueryInterface(entity, IID_Health);
 		if (cmpHealth)
 			cmpHealth.Kill();
 		this.entities.splice(entityIndex, 1);
@@ -707,7 +689,7 @@
 	}
 
 	if (killedEntities.length > 0)
-		Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added" : [], "removed" : killedEntities });
+		Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [], "removed": killedEntities });
 	this.UpdateGarrisonFlag();
 };
 
@@ -716,14 +698,10 @@
  */
 GarrisonHolder.prototype.IsEjectable = function(entity)
 {
-	var ejectableClasses = this.template.EjectClassesOnDestroy._string;
+	let ejectableClasses = this.template.EjectClassesOnDestroy._string;
 	ejectableClasses = ejectableClasses ? ejectableClasses.split(/\s+/) : [];
-	var entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList();
-	for each (var ejectableClass in ejectableClasses)
-		if (entityClasses.indexOf(ejectableClass) != -1)
-			return true;
-
-	return false;
+	let entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList();
+	return ejectableClasses.some(ejectableClass => entityClasses.indexOf(ejectableClass) != -1);
 };
 
 /**
@@ -744,4 +722,3 @@
 };
 
 Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder);
-
Index: binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js	(nonexistent)
+++ binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js	(working copy)
@@ -0,0 +1,128 @@
+Engine.LoadHelperScript("ValueModification.js");
+Engine.LoadHelperScript("Player.js");
+Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
+Engine.LoadComponentScript("GarrisonHolder.js");
+Engine.LoadComponentScript("interfaces/AuraManager.js");
+Engine.LoadComponentScript("interfaces/Auras.js");
+Engine.LoadComponentScript("interfaces/Heal.js");
+Engine.LoadComponentScript("interfaces/Health.js");
+Engine.LoadComponentScript("interfaces/ProductionQueue.js")
+Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/Timer.js");
+Engine.LoadComponentScript("interfaces/UnitAI.js")
+Engine.LoadComponentScript("interfaces/RallyPoint.js");
+
+const garrisonedEntitiesList = [25, 26, 27];
+const garrisonHolderId = 15;
+const unitToGarrisonId = 24;
+const classes = "Infantry+Cavalry";
+
+ResetState();
+
+let holder = garrisonHolderId;
+let unit = unitToGarrisonId;
+
+AddMock(holder, IID_Footprint, {
+	"PickSpawnPointBothPass": () => ({ "x": 4, "y": 3, "z": 30 }),
+	"PickSpawnPoint": () => ({ "x": 4, "y": 3, "z": 30 }),
+});
+AddMock(holder, IID_Health, {
+	"GetHitpoints": () => 500,
+	"GetMaxHitpoints": () => 600,
+});
+AddMock(holder, IID_Obstruction, {
+	"GetUnitRadius": () => 0,
+});
+AddMock(holder, IID_Position, {
+	"SetTurretParent": () => {},
+	"GetTurretParent": () => INVALID_ENTITY,
+	"GetPosition": () => ({ "x": 4, "y": 3, "z": 25 }),
+	"GetRotation": () => ({ "x": 4, "y": 0, "z": 6 }),
+	"IsInWorld": () => true,
+	"GetHeightOffset": () => 0,
+});
+
+AddMock(SYSTEM_ENTITY, IID_Timer, {
+	GetTime: () => 0,
+	SetTimeout: (ent, iid, funcname, time, data) => 0,
+});
+
+for (let i = 24; i < 28; ++i)
+{
+	AddMock(holder, IID_Health, {
+		"GetHitpoints": () => 190,
+		"GetMaxHitpoints": () => 200,
+	});
+	AddMock(i, IID_Identity, {
+		"GetClassesList": () => "Infantry+Cavalry",
+	});
+	AddMock(i, IID_Ownership, {
+		"GetOwner": () => 1,
+	});
+	AddMock(i, IID_UnitAI, {
+		"SetTurretStance": () => {},
+		"CanGarrison": () => true,
+		"IsUnderAlert": () => false,
+		"ResetTurretStance": () => {},
+		"Ungarrison" : () => {},
+	});
+	AddMock(i, IID_Position, {
+		"GetHeightOffset": () => 0,
+		"GetPosition": () => ({ "x": 4, "y": 3, "z": 25 }),
+		"GetRotation": () => ({ "x": 4, "y": 0, "z": 6 }),
+		"GetTurretParent": () => INVALID_ENTITY,
+		"IsInWorld": () => true,
+		"JumpTo": () => {},
+		"MoveOutOfWorld": () => {},
+		"SetHeightOffset":() => {},
+		"SetTurretParent": () => {},
+	});
+
+}
+
+let cmpGarrisonHolder = ConstructComponent(holder, "GarrisonHolder", {
+	"Max" : 4,
+	"List": {"_string": "Infantry+Cavalry"},
+	"EjectHealth": 0.1,
+	"EjectClassesOnDestroy": {"_string": "Infantry"},
+	"BuffHeal": 1,
+	"LoadingRange": 2.1,
+	"Pickup": false,
+	"VisibleGarrisonPoints": {
+		"archer1": {
+			"X": 12,
+			"Y": 5,
+			"Z": 6
+		},
+		"archer2": {
+			"X": 15,
+			"Y": 5,
+			"Z": 6
+		}
+	}
+});
+
+cmpGarrisonHolder.Init();
+
+TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetLoadingRange(), { "max": 2.1, "min": 0 });
+TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);
+TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetHealRate(), 1);
+TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetAllowedClasses(), classes);
+TS_ASSERT_EQUALS(cmpGarrisonHolder.GetCapacity(), 4);
+TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 0);
+TS_ASSERT_EQUALS(cmpGarrisonHolder.CanPickup(unitToGarrisonId), false);
+TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), false);
+TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(unitToGarrisonId), true);
+for (let entity of garrisonedEntitiesList)
+	TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(entity), true);
+TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), true);
+TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [24,25,26,27]);
+TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 4);
+TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(25), true);
+TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [24,26,27]);
+TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 3);
+TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), false);
+TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadAll(), true);
+TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);
+
+
