diff --git a/xUnitRevit/App.cs b/xUnitRevit/App.cs
new file mode 100644
index 0000000..81878e1
--- /dev/null
+++ b/xUnitRevit/App.cs
@@ -0,0 +1,41 @@
+#region Namespaces
+using System;
+using System.Collections.Generic;
+using Autodesk.Revit.ApplicationServices;
+using Autodesk.Revit.Attributes;
+using Autodesk.Revit.DB;
+using Autodesk.Revit.UI;
+#endregion
+
+namespace xUnitRevit
+{
+ class App : IExternalApplication
+ {
+ public Result OnStartup(UIControlledApplication a)
+ {
+#if DEBUG
+ a.ControlledApplication.ApplicationInitialized += ControlledApplication_ApplicationInitialized; ;
+#endif
+
+ return Result.Succeeded;
+ }
+
+
+ private void ControlledApplication_ApplicationInitialized(object sender, Autodesk.Revit.DB.Events.ApplicationInitializedEventArgs e)
+ {
+ Application app = sender as Application;
+ UIApplication uiapp = new UIApplication(app);
+
+
+
+ // uiapp.OpenAndActivateDocument(@"C:\Code\Speckle-Next\SpeckleKitRevit\TestModels\Walls.rvt");
+ TestRunner.Launch(uiapp);
+ }
+
+
+ public Result OnShutdown(UIControlledApplication a)
+ {
+ return Result.Succeeded;
+ }
+ }
+}
diff --git a/xUnitRevit/CmdAvailabilityViews.cs b/xUnitRevit/CmdAvailabilityViews.cs
new file mode 100644
index 0000000..1797c5f
--- /dev/null
+++ b/xUnitRevit/CmdAvailabilityViews.cs
@@ -0,0 +1,22 @@
+using Autodesk.Revit.DB;
+using Autodesk.Revit.UI;
+
+namespace xUnitRevit
+{
+ internal class CmdAvailabilityViews : IExternalCommandAvailability
+ {
+
+ ///
+ /// Command Availability - Views
+ ///
+ ///
+ ///
+ ///
+ public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
+ {
+ //Can be run from any view/state
+ return true;
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/xUnitRevit/Command.cs b/xUnitRevit/Command.cs
new file mode 100644
index 0000000..f972d8f
--- /dev/null
+++ b/xUnitRevit/Command.cs
@@ -0,0 +1,46 @@
+#region Namespaces
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using Autodesk.Revit.ApplicationServices;
+using Autodesk.Revit.Attributes;
+using Autodesk.Revit.DB;
+using Autodesk.Revit.UI;
+using Autodesk.Revit.UI.Selection;
+using Xunit.Runner.Wpf;
+using Xunit.Runner.Wpf.ViewModel;
+#endregion
+
+namespace xUnitRevit
+{
+ [Transaction(TransactionMode.Manual)]
+ public class Command : IExternalCommand
+ {
+ static object consoleLock = new object();
+ static ManualResetEvent finished = new ManualResetEvent(false);
+ static Result result = Result.Succeeded;
+
+ public Result Execute(
+ ExternalCommandData commandData,
+ ref string message,
+ ElementSet elements)
+ {
+ UIApplication uiapp = commandData.Application;
+ TestRunner.Launch(uiapp);
+ return Result.Succeeded;
+
+
+
+ }
+
+
+
+
+
+ }
+
+
+}
diff --git a/xUnitRevit/ExternalEventHandler.cs b/xUnitRevit/ExternalEventHandler.cs
new file mode 100644
index 0000000..3f5688d
--- /dev/null
+++ b/xUnitRevit/ExternalEventHandler.cs
@@ -0,0 +1,47 @@
+using Autodesk.Revit.UI;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace xUnitRevit
+{
+ ///
+ /// Speckle custom event invoker. Has a queue of actions that, in theory, this things should iterate through.
+ /// Actions are added to the queue from the ui bindings (mostly) and then raised.
+ ///
+ public class ExternalEventHandler : IExternalEventHandler
+ {
+
+ public bool Running = false;
+ public List Queue { get; set; }
+
+ public ExternalEventHandler(List queue)
+ {
+ Queue = queue;
+ }
+
+ public void Execute(UIApplication app)
+ {
+ Debug.WriteLine("Current queue len is: " + Queue.Count);
+ if (Running) return; // queue will run itself through
+
+ Running = true;
+
+ Queue[0]();
+
+
+ Queue.RemoveAt(0);
+ Running = false;
+ }
+
+ public string GetName()
+ {
+ return "xUnit Revit";
+ }
+ }
+}
+
+
diff --git a/xUnitRevit/Properties/AssemblyInfo.cs b/xUnitRevit/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..830c8cb
--- /dev/null
+++ b/xUnitRevit/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SpeckleRevitTests")]
+[assembly: AssemblyDescription("Revit Add-In Description for SpeckleRevitTests")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Autodesk Inc.")]
+[assembly: AssemblyProduct("SpeckleRevitTests Revit C# .NET Add-In")]
+[assembly: AssemblyCopyright("Copyright (C) 2020 by Jeremy Tammik, Autodesk Inc.")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("321044f7-b0b2-4b1c-af18-e71a19252be0")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("2021.0.0.0")]
+[assembly: AssemblyFileVersion("2021.0.0.0")]
diff --git a/xUnitRevit/TestRunner.cs b/xUnitRevit/TestRunner.cs
new file mode 100644
index 0000000..627a15d
--- /dev/null
+++ b/xUnitRevit/TestRunner.cs
@@ -0,0 +1,76 @@
+using Autodesk.Revit.UI;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit.Runner.Wpf;
+using Xunit.Runner.Wpf.ViewModel;
+using xUnitRevitUtils;
+
+namespace xUnitRevit
+{
+ public static class TestRunner
+ {
+ public static void Launch(UIApplication uiapp)
+ {
+ try
+ {
+ var queue = new List();
+ var eventHandler = ExternalEvent.Create(new ExternalEventHandler(queue));
+
+ xru.Initialize(uiapp, SynchronizationContext.Current, eventHandler, queue);
+
+ var main = new MainWindow();
+ main.Title = "xUnit Revit by Speckle";
+ /// (main.DataContext as MainViewModel).Injector =
+ (main.DataContext as MainViewModel).StartupAssemblies = new List() { @"C:\Code\Speckle-Next\SpeckleKitRevit\SpeckleRevitTests\bin\Debug\SpeckleRevitTests.dll" };
+ main.Show();
+
+
+ }
+ catch (Exception e)
+ {
+ }
+
+ //return Result.Succeeded;
+ }
+ }
+
+ //public class Injector : IAssemblyInjector
+ //{
+ // private UIApplication uiapp { get; set; }
+ // SynchronizationContext uiContext { get; set; }
+
+ // private List queue { get; set; }
+ // private ExternalEvent eventHandler { get; set; }
+
+ // public Injector(UIApplication uiapp, SynchronizationContext uiContext, ExternalEvent eventHandler, List queue)
+ // {
+ // this.uiapp = uiapp;
+ // this.uiContext = uiContext;
+ // this.eventHandler = eventHandler;
+ // this.queue = queue;
+ // }
+ // public void Inject(Assembly assembly)
+ // {
+ // var types = assembly.GetTypes();
+ // foreach (var type in types)
+ // {
+ // if (type.Name == "xru")
+ // {
+ // if (type.GetProperties().Select(p => p.Name).Contains("Uiapp"))
+ // {
+ // type.GetProperty("Uiapp").SetValue(null, uiapp);
+ // type.GetProperty("UiContext").SetValue(null, uiContext);
+ // type.GetProperty("Queue").SetValue(null, queue);
+ // type.GetProperty("EventHandler").SetValue(null, eventHandler);
+ // }
+ // }
+ // }
+ // }
+
+ //}
+}
diff --git a/xUnitRevit/xUnitRevit.addin b/xUnitRevit/xUnitRevit.addin
new file mode 100644
index 0000000..b1d1b67
--- /dev/null
+++ b/xUnitRevit/xUnitRevit.addin
@@ -0,0 +1,20 @@
+
+
+
+ xUnitRevit
+ xUnitRevit
+ xUnitRevit\xUnitRevit.dll
+ xUnitRevit.Command
+ 9d179887-9fae-41f9-940a-697b59cafe4a
+ speckle
+ Speckle
+
+
+ Application xUnitRevit
+ xUnitRevit\xUnitRevit.dll
+ xUnitRevit.App
+ d9c9ed6f-7b2a-4c47-8f87-a6f33a553d50
+ speckle
+ Speckle
+
+
diff --git a/xUnitRevit/xUnitRevit.csproj b/xUnitRevit/xUnitRevit.csproj
new file mode 100644
index 0000000..ab1dfeb
--- /dev/null
+++ b/xUnitRevit/xUnitRevit.csproj
@@ -0,0 +1,98 @@
+
+
+
+
+
+ None
+
+
+
+
+
+
+ Debug
+ AnyCPU
+ {27A79ACA-7EA8-4406-8BB8-216578CC3AB7}
+ Library
+ Properties
+ xUnitRevit
+ xUnitRevit
+ v4.8
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ Program
+ $(ProgramW6432)\Autodesk\Revit 2021\Revit.exe
+ 2021
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ Program
+ $(ProgramW6432)\Autodesk\Revit 2021\Revit.exe
+ 2021
+
+
+
+
+
+ $(ProgramW6432)\Autodesk\Revit 2021\RevitAPI.dll
+ False
+
+
+ $(ProgramW6432)\Autodesk\Revit 2021\RevitAPIUI.dll
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {4120f4ac-1b24-4b7c-86a9-0a3c75e526cd}
+ xunit.runner.wpf
+
+
+ {9406d1d6-0751-4f22-9a0e-87acfbe17365}
+ xUnitRevitUtils
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xUnitRevitUtils/xUnitRevitUtils.csproj b/xUnitRevitUtils/xUnitRevitUtils.csproj
new file mode 100644
index 0000000..fe19c8d
--- /dev/null
+++ b/xUnitRevitUtils/xUnitRevitUtils.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
diff --git a/xUnitRevitUtils/xru.cs b/xUnitRevitUtils/xru.cs
new file mode 100644
index 0000000..e8b0912
--- /dev/null
+++ b/xUnitRevitUtils/xru.cs
@@ -0,0 +1,115 @@
+using Autodesk.Revit.DB;
+using Autodesk.Revit.UI;
+using Autodesk.Revit.UI.Selection;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace xUnitRevitUtils
+{
+ public static class xru
+ {
+ private static UIApplication Uiapp { get; set; }
+
+ private static List Queue { get; set; }
+ private static ExternalEvent EventHandler { get; set; }
+
+ private static SynchronizationContext UiContext { get; set; }
+
+ public static Document ActiveDoc { get { return Uiapp.ActiveUIDocument.Document; } }
+ public static Selection CuerrentSelection { get { return Uiapp.ActiveUIDocument.Selection; } }
+
+ public static void Initialize(UIApplication uiapp, SynchronizationContext uiContext, ExternalEvent eventHandler, List queue)
+ {
+ Uiapp = uiapp;
+ UiContext = uiContext;
+ EventHandler = eventHandler;
+ Queue = queue;
+ }
+
+ public static List GetActiveSelection()
+ {
+ if (Uiapp.ActiveUIDocument != null)
+ return Uiapp.ActiveUIDocument.Selection.GetElementIds().Select(x => Uiapp.ActiveUIDocument.Document.GetElement(x)).ToList();
+ return new List();
+ }
+ ///
+ /// Opens and activates a document if not open already
+ ///
+ ///
+ public static Document OpenDoc(string path)
+ {
+ Assert.NotNull(Uiapp);
+ Document doc = null;
+ //OpenAndActivateDocument only works if run from the current context
+ UiContext.Send(x => { doc = Uiapp.OpenAndActivateDocument(path).Document; }, null);
+ Assert.NotNull(doc);
+ return doc;
+ }
+
+
+ ///
+ /// Creates a new empty document
+ ///
+ ///
+ public static Document CreateNewDoc(string templatePath, string filePath)
+ {
+ Assert.NotNull(Uiapp);
+ Document doc = null;
+
+ try
+ {
+ if (File.Exists(filePath))
+ File.Delete(filePath);
+ }
+ catch { }
+
+ //OpenAndActivateDocument only works if run from the current context
+ UiContext.Send(x =>
+ {
+ if (!File.Exists(filePath))
+ {
+ doc = Uiapp.Application.NewProjectDocument(templatePath);
+ doc.SaveAs(filePath);
+ doc.Close();
+ }
+
+ doc = Uiapp.OpenAndActivateDocument(filePath).Document;
+ }
+ , null);
+ Assert.NotNull(doc);
+ return doc;
+ }
+
+ public static Task RunInTransaction(Action action, Document doc)
+ {
+ var tcs = new TaskCompletionSource();
+ Queue.Add(new Action(() =>
+ {
+ try
+ {
+ using (Transaction transaction = new Transaction(doc, "test transaction"))
+ {
+ transaction.Start();
+ action.Invoke();
+ transaction.Commit();
+
+ }
+ }
+ catch (Exception e)
+ {
+ tcs.TrySetException(e);
+ }
+ tcs.TrySetResult("");
+ }));
+
+ EventHandler.Raise();
+
+ return tcs.Task;
+ }
+ }
+}