diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 5290c1f9e7..bff23e6166 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -22,6 +22,7 @@ add_executable(drtest
Models.cpp
PatchIterators.cpp
PatchWelding.cpp
+ Prefabs.cpp
SelectionAlgorithm.cpp
Selection.cpp
VFS.cpp
diff --git a/test/Prefabs.cpp b/test/Prefabs.cpp
new file mode 100644
index 0000000000..845de55122
--- /dev/null
+++ b/test/Prefabs.cpp
@@ -0,0 +1,99 @@
+#include "RadiantTest.h"
+
+#include "icommandsystem.h"
+#include "ispeakernode.h"
+#include "ilightnode.h"
+#include "ibrush.h"
+#include "scene/PrefabBoundsAccumulator.h"
+#include "os/path.h"
+#include "algorithm/Scene.h"
+
+namespace test
+{
+
+class PrefabTest : public RadiantTest
+{
+protected:
+ void loadPrefabAtOrigin(const std::string& prefabFolderRelativePath)
+ {
+ fs::path prefabPath = _context.getTestProjectPath();
+ prefabPath /= "prefabs";
+ prefabPath /= prefabFolderRelativePath;
+
+ GlobalCommandSystem().executeCommand("LoadPrefabAt", prefabPath.string(), Vector3(0, 0, 0), 1);
+ }
+};
+
+TEST_F(PrefabTest, LoadPrefabAt)
+{
+ loadPrefabAtOrigin("large_bounds.pfbx");
+
+ auto numSpeakers = algorithm::getChildCount(GlobalMapModule().getRoot(), [](const scene::INodePtr& node)
+ {
+ return Node_getSpeakerNode(node) != nullptr;
+ });
+
+ auto numLights = algorithm::getChildCount(GlobalMapModule().getRoot(), [](const scene::INodePtr& node)
+ {
+ return Node_getLightNode(node) != nullptr;
+ });
+
+ auto numBrushes = algorithm::getChildCount(GlobalMapModule().getWorldspawn(), [](const scene::INodePtr& node)
+ {
+ return Node_getIBrush(node) != nullptr;
+ });
+
+ EXPECT_EQ(numSpeakers, 1);
+ EXPECT_EQ(numLights, 1);
+ EXPECT_EQ(numBrushes, 1);
+}
+
+TEST_F(PrefabTest, PrefabBoundsCalculation)
+{
+ loadPrefabAtOrigin("large_bounds.pfbx");
+
+ EXPECT_NE(GlobalSelectionSystem().countSelected(), 0);
+
+ scene::PrefabBoundsAccumulator accumulator;
+ GlobalSelectionSystem().foreachSelected(accumulator);
+
+ // Speaker and light bounds should not influence the prefab bounds
+ EXPECT_EQ(accumulator.getBounds().getExtents(), Vector3(32, 16, 64));
+}
+
+TEST_F(PrefabTest, PrefabBoundsAccumulatorGetNodeBounds)
+{
+ loadPrefabAtOrigin("large_bounds.pfbx");
+
+ // Prefab bounds calculation should ignore the speaker radius
+ auto speaker = algorithm::getEntityByName(GlobalMapModule().getRoot(), "speaker_1");
+ auto bounds = scene::PrefabBoundsAccumulator::GetNodeBounds(speaker);
+ EXPECT_EQ(bounds.getExtents(), Vector3(8, 8, 8));
+
+ // Prefab bounds calculation should ignore the light volume
+ auto light = algorithm::getEntityByName(GlobalMapModule().getRoot(), "light_1");
+ bounds = scene::PrefabBoundsAccumulator::GetNodeBounds(light);
+ EXPECT_EQ(bounds.getExtents(), Vector3(8, 8, 8));
+}
+
+TEST_F(PrefabTest, PrefabInsertPosition)
+{
+ // The saved prefab is slightly off-center at 128 0 0
+ // When inserting it at 0,0,0 the insertion code should compensate the offset
+
+ fs::path prefabPath = _context.getTestProjectPath();
+ prefabPath /= "prefabs/large_bounds.pfbx";
+
+ GlobalCommandSystem().executeCommand("LoadPrefabAt", prefabPath.string(), Vector3(0, 0, 0), 1);
+
+ auto speaker = algorithm::getEntityByName(GlobalMapModule().getRoot(), "speaker_1");
+ EXPECT_EQ(speaker->worldAABB().getOrigin(), Vector3(0, 0, 0));
+
+ auto light = algorithm::getEntityByName(GlobalMapModule().getRoot(), "light_1");
+ EXPECT_EQ(light->worldAABB().getOrigin(), Vector3(0, 0, 0));
+
+ auto brush = algorithm::findFirstBrushWithMaterial(GlobalMapModule().getWorldspawn(), "textures/common/caulk");
+ EXPECT_EQ(brush->worldAABB().getOrigin(), Vector3(0, 0, 0));
+}
+
+}
diff --git a/test/resources/tdm/def/speaker.def b/test/resources/tdm/def/speaker.def
new file mode 100644
index 0000000000..2095f95fea
--- /dev/null
+++ b/test/resources/tdm/def/speaker.def
@@ -0,0 +1,9 @@
+entityDef speaker
+{
+ "editor_color" "0 1 0"
+ "editor_mins" "-8 -8 -8"
+ "editor_maxs" "8 8 8"
+
+ "editor_displayFolder" "Sound"
+ "spawnclass" "idSound"
+}
\ No newline at end of file
diff --git a/test/resources/tdm/prefabs/large_bounds.pfbx b/test/resources/tdm/prefabs/large_bounds.pfbx
new file mode 100644
index 0000000000..3146b85717
--- /dev/null
+++ b/test/resources/tdm/prefabs/large_bounds.pfbx
@@ -0,0 +1,100 @@
+
+
diff --git a/tools/msvc/Tests/Tests.vcxproj b/tools/msvc/Tests/Tests.vcxproj
index dfd0afa8f2..a621fd08d9 100644
--- a/tools/msvc/Tests/Tests.vcxproj
+++ b/tools/msvc/Tests/Tests.vcxproj
@@ -91,6 +91,7 @@
+
diff --git a/tools/msvc/Tests/Tests.vcxproj.filters b/tools/msvc/Tests/Tests.vcxproj.filters
index e5bfc069b9..9b3213a90a 100644
--- a/tools/msvc/Tests/Tests.vcxproj.filters
+++ b/tools/msvc/Tests/Tests.vcxproj.filters
@@ -36,6 +36,7 @@
+