diff --git a/.gitignore b/.gitignore
index f4eb769..078e9c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,6 +50,10 @@ HumanUI/HumanUI/bin/
HUI_Install/bin/
HUI_Install/obj/
HumanUIBaseApp/HumanUIBaseApp/bin/*
+HumanUI.Tests/bin/
+HumanUI.Tests/obj/
+HumanUI.Tests/diag*.log
+HumanUI.Tests/diag.host*
# NuGet Dependencies
packages/
diff --git a/HumanUI.Tests/ComponentSmokeTests.cs b/HumanUI.Tests/ComponentSmokeTests.cs
new file mode 100644
index 0000000..b745f14
--- /dev/null
+++ b/HumanUI.Tests/ComponentSmokeTests.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Grasshopper.Kernel;
+using Xunit;
+
+namespace HumanUI.Tests
+{
+ ///
+ /// Smoke tests over every GH_Component subclass in the HumanUI assembly. These sit above
+ /// the UI toolkit, so they survive a WPF -> Eto migration: they exercise the Grasshopper
+ /// metadata layer (Name, Nickname, Category, ComponentGuid uniqueness) and instantiability.
+ ///
+ public class ComponentSmokeTests
+ {
+ private static readonly Type[] ComponentTypes = LoadComponentTypes();
+
+ private static Type[] LoadComponentTypes()
+ {
+ Assembly asm = typeof(HumanUIInfo).Assembly;
+ return asm.GetTypes()
+ .Where(t => !t.IsAbstract
+ && typeof(GH_Component).IsAssignableFrom(t)
+ && t.GetConstructor(Type.EmptyTypes) != null)
+ .ToArray();
+ }
+
+ public static IEnumerable