From c4aa8f5badefb493f491fe42fa8606165d3b5b26 Mon Sep 17 00:00:00 2001
From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com>
Date: Thu, 5 Jun 2025 12:55:47 -0500
Subject: [PATCH 01/14] Alpha
---
.../ProjectTemplate.csproj | 12 +--
.../Reqnroll.VisualStudio.csproj | 5 ++
.../Resources/TestFrameworkDescriptors.json | 14 +++
.../AddNewReqnrollProjectViewModel.cs | 5 ++
.../NewProjectMetaDataProvider.cs | 87 +++++++++++++++++++
.../Wizards/ReqnrollProjectWizard.cs | 12 ++-
6 files changed, 121 insertions(+), 14 deletions(-)
create mode 100644 Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
create mode 100644 Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
diff --git a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
index af39a4ff..a0291dc4 100644
--- a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
+++ b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
@@ -7,16 +7,8 @@
- $if$ ('$unittestframework$' == 'xUnit')
-
-
- $endif$$if$ ('$unittestframework$' == 'NUnit')
-
-
- $endif$$if$ ('$unittestframework$' == 'MSTest')
-
-
- $endif$$if$ ('$fluentassertionsincluded$' == 'True')
+
+ $unittestframework$$if$ ('$fluentassertionsincluded$' == 'True')
$endif$
diff --git a/Reqnroll.VisualStudio/Reqnroll.VisualStudio.csproj b/Reqnroll.VisualStudio/Reqnroll.VisualStudio.csproj
index d7ff0e86..11f3b47f 100644
--- a/Reqnroll.VisualStudio/Reqnroll.VisualStudio.csproj
+++ b/Reqnroll.VisualStudio/Reqnroll.VisualStudio.csproj
@@ -8,8 +8,13 @@
TRACE;
+
+
+
+
+
diff --git a/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json b/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
new file mode 100644
index 00000000..c202368a
--- /dev/null
+++ b/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
@@ -0,0 +1,14 @@
+{
+ "xUnit": {
+ "description": "Use xUnit v2 test executor with Reqnroll",
+ "dependencies": "\n\n"
+ },
+ "MsTest": {
+ "description": "Use MsTest v3 test executor with Reqnroll",
+ "dependencies": "\n\n"
+ },
+ "NUnit": {
+ "description": "Use NUnit v3 test executor with Reqnroll",
+ "dependencies": "\n\n"
+ }
+}
\ No newline at end of file
diff --git a/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs b/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs
index 50c42a64..0638344f 100644
--- a/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs
+++ b/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs
@@ -6,6 +6,11 @@ public class AddNewReqnrollProjectViewModel : INotifyPropertyChanged
private const string MsTest = "MsTest";
private const string Net8 = "net8.0";
+ public AddNewReqnrollProjectViewModel() { }
+ public AddNewReqnrollProjectViewModel(IEnumerable testFrameworkNames)
+ {
+ TestFrameworks = new (testFrameworkNames);
+ }
#if DEBUG
public static AddNewReqnrollProjectViewModel DesignData = new()
{
diff --git a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
new file mode 100644
index 00000000..06ecab12
--- /dev/null
+++ b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
@@ -0,0 +1,87 @@
+namespace Reqnroll.VisualStudio.Wizards.Infrastructure;
+
+public interface INewProjectMetaDataProvider
+{
+ IEnumerable TestFrameworks { get; }
+ string DependenciesOf(string testFramework);
+}
+public record TestFrameworkInfoModel(string description, string dependencies);
+
+[Export(typeof(INewProjectMetaDataProvider))]
+public class NewProjectMetaDataProvider : INewProjectMetaDataProvider
+{
+ private Dictionary _testFrameworkDescriptors = new();
+ private Task> _httpTask;
+ private Lazy> _resolvedTestFrameworkDescriptors;
+
+ [ImportingConstructor]
+ public NewProjectMetaDataProvider()
+ {
+ // read static metadata from a resource file, deserialize the resulting json, storing it in the _testFrameworkDescriptors field
+ var resourceName = "Reqnroll.VisualStudio.Resources.TestFrameworkDescriptors.json";
+ using (var stream = typeof(NewProjectMetaDataProvider).Assembly.GetManifestResourceStream(resourceName))
+ using (var reader = new StreamReader(stream))
+ {
+ var json = reader.ReadToEnd();
+ var data = JsonSerialization.DeserializeObject>(json);
+ if (data != null)
+ _testFrameworkDescriptors = data;
+ }
+
+ // launch a task to read metadata from http resource and deserialize the resulting json, save the Task to a field
+ _httpTask = Task.Run(async () =>
+ {
+ try
+ {
+ using (var httpClient = new System.Net.Http.HttpClient())
+ {
+ var httpJson = await httpClient.GetStringAsync("https://example.com/testframeworks.json").ConfigureAwait(false);
+ var httpData = JsonSerialization.DeserializeObject>(httpJson);
+ return httpData;
+ }
+ }
+ catch
+ {
+ return null;
+ }
+ });
+
+ _resolvedTestFrameworkDescriptors = new Lazy>(FinishRetrievalOfTestFrameworkDescriptors);
+ }
+
+ public IEnumerable TestFrameworks
+ {
+ get
+ {
+ var descriptors = _resolvedTestFrameworkDescriptors.Value;
+ return descriptors.Keys;
+ }
+ }
+
+ public string DependenciesOf(string testFramework)
+ {
+ var descriptors = _resolvedTestFrameworkDescriptors.Value;
+ return descriptors[testFramework].dependencies.Replace("\\n", Environment.NewLine);
+ }
+
+ private Dictionary FinishRetrievalOfTestFrameworkDescriptors()
+ {
+ // Wait for the HTTP task to complete
+ if (_httpTask == null)
+ return _testFrameworkDescriptors;
+
+ try
+ {
+ var result = _httpTask.GetAwaiter().GetResult();
+ if (result != null)
+ {
+ _testFrameworkDescriptors = result;
+ }
+ }
+ catch
+ {
+ // Ignore exceptions, keep existing _testFrameworkDescriptors
+ }
+ return _testFrameworkDescriptors;
+ }
+}
diff --git a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
index fc783a2d..69f216e3 100644
--- a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
+++ b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
@@ -10,28 +10,32 @@ public class ReqnrollProjectWizard : IDeveroomWizard
{
private readonly IDeveroomWindowManager _deveroomWindowManager;
private readonly IMonitoringService _monitoringService;
+ private readonly INewProjectMetaDataProvider _newProjectMetaDataProvider;
[ImportingConstructor]
- public ReqnrollProjectWizard(IDeveroomWindowManager deveroomWindowManager, IMonitoringService monitoringService)
+ public ReqnrollProjectWizard(IDeveroomWindowManager deveroomWindowManager, IMonitoringService monitoringService, INewProjectMetaDataProvider newProjectMetaDataProvider)
{
_deveroomWindowManager = deveroomWindowManager;
_monitoringService = monitoringService;
+ _newProjectMetaDataProvider = newProjectMetaDataProvider;
}
public bool RunStarted(WizardRunParameters wizardRunParameters)
{
_monitoringService.MonitorProjectTemplateWizardStarted();
- var viewModel = new AddNewReqnrollProjectViewModel();
+ var frameworkNames = _newProjectMetaDataProvider.TestFrameworks;
+
+ var viewModel = new AddNewReqnrollProjectViewModel(frameworkNames);
var dialogResult = _deveroomWindowManager.ShowDialog(viewModel);
if (!dialogResult.HasValue || !dialogResult.Value) return false;
_monitoringService.MonitorProjectTemplateWizardCompleted(viewModel.DotNetFramework, viewModel.UnitTestFramework,
viewModel.FluentAssertionsIncluded);
-
+ var packageReferencesToInclude = _newProjectMetaDataProvider.DependenciesOf(viewModel.UnitTestFramework);
// Add custom parameters.
wizardRunParameters.ReplacementsDictionary.Add("$dotnetframework$", viewModel.DotNetFramework);
- wizardRunParameters.ReplacementsDictionary.Add("$unittestframework$", viewModel.UnitTestFramework);
+ wizardRunParameters.ReplacementsDictionary.Add("$unittestframework$", packageReferencesToInclude);
wizardRunParameters.ReplacementsDictionary.Add("$fluentassertionsincluded$",
viewModel.FluentAssertionsIncluded.ToString(CultureInfo.InvariantCulture));
From 0be9ff0f6cac8ba876349cf6e59507195f966850 Mon Sep 17 00:00:00 2001
From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com>
Date: Thu, 5 Jun 2025 17:50:30 -0500
Subject: [PATCH 02/14] Revised substitution model.
---
.../ProjectTemplate.csproj | 5 ++-
.../Resources/TestFrameworkDescriptors.json | 45 +++++++++++++++++--
.../NewProjectMetaDataProvider.cs | 9 ++--
.../Wizards/ReqnrollProjectWizard.cs | 13 +++++-
4 files changed, 62 insertions(+), 10 deletions(-)
diff --git a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
index a0291dc4..3525fdcd 100644
--- a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
+++ b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
@@ -8,7 +8,10 @@
- $unittestframework$$if$ ('$fluentassertionsincluded$' == 'True')
+ $Reqnroll_Lib$
+ $TestFramework_Lib$
+ $Adapter_Lib$
+ $if$ ('$fluentassertionsincluded$' == 'True')
$endif$
diff --git a/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json b/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
index c202368a..10944a18 100644
--- a/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
+++ b/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
@@ -1,14 +1,53 @@
{
"xUnit": {
"description": "Use xUnit v2 test executor with Reqnroll",
- "dependencies": "\n\n"
+ "dependencies": {
+ "Reqnroll_Lib": {
+ "name": "Reqnroll.xUnit",
+ "version": "2.4.1"
+ },
+ "TestFramework_Lib": {
+ "name": "xunit",
+ "version": "2.8.1"
+ },
+ "Adapter_Lib": {
+ "name": "xunit.runner.visualstudio",
+ "version": "2.8.1"
+ }
+ }
},
"MsTest": {
"description": "Use MsTest v3 test executor with Reqnroll",
- "dependencies": "\n\n"
+ "dependencies": {
+ "Reqnroll_Lib": {
+ "name": "Reqnroll.MsTest",
+ "version": "2.4.1"
+ },
+ "TestFramework_Lib": {
+ "name": "MSTest.TestFramework",
+ "version": "3.4.3"
+ },
+ "Adapter_Lib": {
+ "name": "MSTest.TestAdapter",
+ "version": "3.4.3"
+ }
+ }
},
"NUnit": {
"description": "Use NUnit v3 test executor with Reqnroll",
- "dependencies": "\n\n"
+ "dependencies": {
+ "Reqnroll_Lib": {
+ "name": "Reqnroll.NUnit",
+ "version": "2.4.1"
+ },
+ "TestFramework_Lib": {
+ "name": "nunit",
+ "version": "3.14.0"
+ },
+ "Adapter_Lib": {
+ "name": "NUnit3TestAdapter",
+ "version": "4.5.0"
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
index 06ecab12..e9735c58 100644
--- a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
+++ b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
@@ -3,9 +3,10 @@ namespace Reqnroll.VisualStudio.Wizards.Infrastructure;
public interface INewProjectMetaDataProvider
{
IEnumerable TestFrameworks { get; }
- string DependenciesOf(string testFramework);
+ IDictionary DependenciesOf(string testFramework);
}
-public record TestFrameworkInfoModel(string description, string dependencies);
+public record NugetPackageDescriptor(string name, string version);
+public record TestFrameworkInfoModel(string description, Dictionary dependencies);
[Export(typeof(INewProjectMetaDataProvider))]
public class NewProjectMetaDataProvider : INewProjectMetaDataProvider
@@ -58,10 +59,10 @@ public IEnumerable TestFrameworks
}
}
- public string DependenciesOf(string testFramework)
+ public IDictionary DependenciesOf(string testFramework)
{
var descriptors = _resolvedTestFrameworkDescriptors.Value;
- return descriptors[testFramework].dependencies.Replace("\\n", Environment.NewLine);
+ return descriptors[testFramework].dependencies;
}
private Dictionary FinishRetrievalOfTestFrameworkDescriptors()
diff --git a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
index 69f216e3..6c0d0494 100644
--- a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
+++ b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
@@ -32,10 +32,19 @@ public bool RunStarted(WizardRunParameters wizardRunParameters)
_monitoringService.MonitorProjectTemplateWizardCompleted(viewModel.DotNetFramework, viewModel.UnitTestFramework,
viewModel.FluentAssertionsIncluded);
- var packageReferencesToInclude = _newProjectMetaDataProvider.DependenciesOf(viewModel.UnitTestFramework);
+
+ var dependencies = _newProjectMetaDataProvider.DependenciesOf(viewModel.UnitTestFramework);
+ var keys = new List() { "Reqnroll_Lib", "TestFramework_Lib", "Adapter_Lib" };
// Add custom parameters.
+ foreach(string k in keys)
+ {
+ var package = dependencies[k];
+ var name = package.name;
+ var version = package.version;
+ wizardRunParameters.ReplacementsDictionary.Add($"${k}$",$"");
+ }
+
wizardRunParameters.ReplacementsDictionary.Add("$dotnetframework$", viewModel.DotNetFramework);
- wizardRunParameters.ReplacementsDictionary.Add("$unittestframework$", packageReferencesToInclude);
wizardRunParameters.ReplacementsDictionary.Add("$fluentassertionsincluded$",
viewModel.FluentAssertionsIncluded.ToString(CultureInfo.InvariantCulture));
From 1e8d88dd3c9dc302d7bf30f9f58d861131f9f3ed Mon Sep 17 00:00:00 2001
From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com>
Date: Fri, 6 Jun 2025 13:46:26 -0500
Subject: [PATCH 03/14] Working substitution model with all lowercase and no
underscores
---
.../ProjectTemplate.csproj | 7 +++----
.../Resources/TestFrameworkDescriptors.json | 18 +++++++++---------
.../Wizards/ReqnrollProjectWizard.cs | 2 +-
3 files changed, 13 insertions(+), 14 deletions(-)
diff --git a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
index 3525fdcd..582c4d28 100644
--- a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
+++ b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
@@ -8,10 +8,9 @@
- $Reqnroll_Lib$
- $TestFramework_Lib$
- $Adapter_Lib$
- $if$ ('$fluentassertionsincluded$' == 'True')
+ $reqnrolllib$
+ $testframeworklib$
+ $adapterlib$$if$ ('$fluentassertionsincluded$' == 'True')
$endif$
diff --git a/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json b/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
index 10944a18..2f9773b3 100644
--- a/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
+++ b/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
@@ -2,15 +2,15 @@
"xUnit": {
"description": "Use xUnit v2 test executor with Reqnroll",
"dependencies": {
- "Reqnroll_Lib": {
+ "reqnrolllib": {
"name": "Reqnroll.xUnit",
"version": "2.4.1"
},
- "TestFramework_Lib": {
+ "testframeworklib": {
"name": "xunit",
"version": "2.8.1"
},
- "Adapter_Lib": {
+ "adapterlib": {
"name": "xunit.runner.visualstudio",
"version": "2.8.1"
}
@@ -19,15 +19,15 @@
"MsTest": {
"description": "Use MsTest v3 test executor with Reqnroll",
"dependencies": {
- "Reqnroll_Lib": {
+ "reqnrolllib": {
"name": "Reqnroll.MsTest",
"version": "2.4.1"
},
- "TestFramework_Lib": {
+ "testframeworklib": {
"name": "MSTest.TestFramework",
"version": "3.4.3"
},
- "Adapter_Lib": {
+ "adapterlib": {
"name": "MSTest.TestAdapter",
"version": "3.4.3"
}
@@ -36,15 +36,15 @@
"NUnit": {
"description": "Use NUnit v3 test executor with Reqnroll",
"dependencies": {
- "Reqnroll_Lib": {
+ "reqnrolllib": {
"name": "Reqnroll.NUnit",
"version": "2.4.1"
},
- "TestFramework_Lib": {
+ "testframeworklib": {
"name": "nunit",
"version": "3.14.0"
},
- "Adapter_Lib": {
+ "adapterlib": {
"name": "NUnit3TestAdapter",
"version": "4.5.0"
}
diff --git a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
index 6c0d0494..b2bf73c9 100644
--- a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
+++ b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
@@ -34,7 +34,7 @@ public bool RunStarted(WizardRunParameters wizardRunParameters)
viewModel.FluentAssertionsIncluded);
var dependencies = _newProjectMetaDataProvider.DependenciesOf(viewModel.UnitTestFramework);
- var keys = new List() { "Reqnroll_Lib", "TestFramework_Lib", "Adapter_Lib" };
+ var keys = new List() { "reqnrolllib", "testframeworklib", "adapterlib" };
// Add custom parameters.
foreach(string k in keys)
{
From d09cdbe118407634b256c8395ff8265d59784405 Mon Sep 17 00:00:00 2001
From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com>
Date: Sat, 7 Jun 2025 10:08:33 -0500
Subject: [PATCH 04/14] Working example using a fixed number of slots for
packages.
---
.../ProjectTemplate.csproj | 17 ++++++----
.../Resources/TestFrameworkDescriptors.json | 32 +++++++++----------
.../NewProjectMetaDataProvider.cs | 21 ++++++------
.../Wizards/ReqnrollProjectWizard.cs | 28 +++++++++++++---
4 files changed, 61 insertions(+), 37 deletions(-)
diff --git a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
index 582c4d28..33fbf9c9 100644
--- a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
+++ b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
@@ -6,12 +6,17 @@
enable
-
-
- $reqnrolllib$
- $testframeworklib$
- $adapterlib$$if$ ('$fluentassertionsincluded$' == 'True')
- $endif$
+ $if$ ('$packagerefhasvalue0$' == 'True')
+ $endif$$if$ ('$packagerefhasvalue1$' == 'True')
+ $endif$$if$ ('$packagerefhasvalue2$' == 'True')
+ $endif$$if$ ('$packagerefhasvalue3$' == 'True')
+ $endif$$if$ ('$packagerefhasvalue4$' == 'True')
+ $endif$$if$ ('$packagerefhasvalue5$' == 'True')
+ $endif$$if$ ('$packagerefhasvalue6$' == 'True')
+ $endif$$if$ ('$packagerefhasvalue7$' == 'True')
+ $endif$$if$ ('$packagerefhasvalue8$' == 'True')
+ $endif$$if$ ('$packagerefhasvalue9$' == 'True')
+ $endif$
diff --git a/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json b/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
index 2f9773b3..f3c09490 100644
--- a/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
+++ b/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
@@ -1,53 +1,53 @@
{
"xUnit": {
"description": "Use xUnit v2 test executor with Reqnroll",
- "dependencies": {
- "reqnrolllib": {
+ "dependencies": [
+ {
"name": "Reqnroll.xUnit",
"version": "2.4.1"
},
- "testframeworklib": {
+ {
"name": "xunit",
"version": "2.8.1"
},
- "adapterlib": {
+ {
"name": "xunit.runner.visualstudio",
"version": "2.8.1"
}
- }
+ ]
},
"MsTest": {
"description": "Use MsTest v3 test executor with Reqnroll",
- "dependencies": {
- "reqnrolllib": {
+ "dependencies": [
+ {
"name": "Reqnroll.MsTest",
"version": "2.4.1"
},
- "testframeworklib": {
+ {
"name": "MSTest.TestFramework",
"version": "3.4.3"
},
- "adapterlib": {
+ {
"name": "MSTest.TestAdapter",
"version": "3.4.3"
}
- }
+ ]
},
"NUnit": {
"description": "Use NUnit v3 test executor with Reqnroll",
- "dependencies": {
- "reqnrolllib": {
+ "dependencies": [
+ {
"name": "Reqnroll.NUnit",
"version": "2.4.1"
},
- "testframeworklib": {
+ {
"name": "nunit",
"version": "3.14.0"
},
- "adapterlib": {
+ {
"name": "NUnit3TestAdapter",
"version": "4.5.0"
}
- }
+ ]
}
-}
\ No newline at end of file
+}
diff --git a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
index e9735c58..aa6d0540 100644
--- a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
+++ b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
@@ -3,22 +3,22 @@ namespace Reqnroll.VisualStudio.Wizards.Infrastructure;
public interface INewProjectMetaDataProvider
{
IEnumerable TestFrameworks { get; }
- IDictionary DependenciesOf(string testFramework);
+ IEnumerable DependenciesOf(string testFramework);
}
public record NugetPackageDescriptor(string name, string version);
-public record TestFrameworkInfoModel(string description, Dictionary dependencies);
+public record TestFrameworkInfoModel(string description, IEnumerable dependencies);
[Export(typeof(INewProjectMetaDataProvider))]
public class NewProjectMetaDataProvider : INewProjectMetaDataProvider
{
- private Dictionary _testFrameworkDescriptors = new();
+ private Dictionary _fallbackTestFrameworkDescriptors = new();
private Task> _httpTask;
private Lazy> _resolvedTestFrameworkDescriptors;
[ImportingConstructor]
public NewProjectMetaDataProvider()
{
- // read static metadata from a resource file, deserialize the resulting json, storing it in the _testFrameworkDescriptors field
+ // read static metadata from a resource file, deserialize the resulting json, storing it in the _fallbackTestFrameworkDescriptors field
var resourceName = "Reqnroll.VisualStudio.Resources.TestFrameworkDescriptors.json";
using (var stream = typeof(NewProjectMetaDataProvider).Assembly.GetManifestResourceStream(resourceName))
using (var reader = new StreamReader(stream))
@@ -26,7 +26,7 @@ public NewProjectMetaDataProvider()
var json = reader.ReadToEnd();
var data = JsonSerialization.DeserializeObject>(json);
if (data != null)
- _testFrameworkDescriptors = data;
+ _fallbackTestFrameworkDescriptors = data;
}
// launch a task to read metadata from http resource and deserialize the resulting json, save the Task to a field
@@ -59,7 +59,7 @@ public IEnumerable TestFrameworks
}
}
- public IDictionary DependenciesOf(string testFramework)
+ public IEnumerable DependenciesOf(string testFramework)
{
var descriptors = _resolvedTestFrameworkDescriptors.Value;
return descriptors[testFramework].dependencies;
@@ -69,20 +69,21 @@ private Dictionary FinishRetrievalOfTestFramewor
{
// Wait for the HTTP task to complete
if (_httpTask == null)
- return _testFrameworkDescriptors;
+ return _fallbackTestFrameworkDescriptors;
try
{
var result = _httpTask.GetAwaiter().GetResult();
if (result != null)
{
- _testFrameworkDescriptors = result;
+ return result;
}
}
catch
{
- // Ignore exceptions, keep existing _testFrameworkDescriptors
+ // Ignore exceptions, keep existing _fallbackTestFrameworkDescriptors
}
- return _testFrameworkDescriptors;
+ // If we've gotten to this point, the http end point returned no result or failed. So, we fall back.
+ return _fallbackTestFrameworkDescriptors;
}
}
diff --git a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
index b2bf73c9..f69463a8 100644
--- a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
+++ b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
@@ -33,21 +33,39 @@ public bool RunStarted(WizardRunParameters wizardRunParameters)
_monitoringService.MonitorProjectTemplateWizardCompleted(viewModel.DotNetFramework, viewModel.UnitTestFramework,
viewModel.FluentAssertionsIncluded);
+ int packageIndex = 0;
+
+ // insert set of replacement variables for the SDK package
+ AddPackageToReplacementDictionary(wizardRunParameters, "Microsoft.NET.Test.Sdk", "17.10.0", packageIndex);
+
var dependencies = _newProjectMetaDataProvider.DependenciesOf(viewModel.UnitTestFramework);
- var keys = new List() { "reqnrolllib", "testframeworklib", "adapterlib" };
- // Add custom parameters.
- foreach(string k in keys)
+
+ foreach (var package in dependencies)
{
- var package = dependencies[k];
+ packageIndex++;
var name = package.name;
var version = package.version;
- wizardRunParameters.ReplacementsDictionary.Add($"${k}$",$"");
+ AddPackageToReplacementDictionary(wizardRunParameters, name, version, packageIndex);
}
+ if (viewModel.FluentAssertionsIncluded)
+ AddPackageToReplacementDictionary(wizardRunParameters, "FluentAssertions", "6.12.0", packageIndex+1);
+
wizardRunParameters.ReplacementsDictionary.Add("$dotnetframework$", viewModel.DotNetFramework);
wizardRunParameters.ReplacementsDictionary.Add("$fluentassertionsincluded$",
viewModel.FluentAssertionsIncluded.ToString(CultureInfo.InvariantCulture));
return true;
+
+ static void AddPackageToReplacementDictionary(WizardRunParameters wizardRunParameters, string name, string version, int packageIndex)
+ {
+ string packagename = "packagerefname";
+ string packageversion = "packagerefversion";
+ string packagehasvalue = "packagerefhasvalue";
+
+ wizardRunParameters.ReplacementsDictionary.Add($"${packagehasvalue}{packageIndex}$", true.ToString(CultureInfo.InvariantCulture));
+ wizardRunParameters.ReplacementsDictionary.Add($"${packagename}{packageIndex}$", name);
+ wizardRunParameters.ReplacementsDictionary.Add($"${packageversion}{packageIndex}$", version);
+ }
}
}
From b28fd7c5d94c1e13601a161289d7333b94851081 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?=
Date: Tue, 10 Jun 2025 15:21:06 +0200
Subject: [PATCH 05/14] dummy implementation for single template param
reference substitution
---
.../ProjectTemplate.csproj | 4 +++
.../Wizards/ReqnrollProjectWizard.cs | 28 ++++++++++++++-----
2 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
index 33fbf9c9..04ce7696 100644
--- a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
+++ b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
@@ -19,6 +19,10 @@
$endif$
+
+ $foo$
+
+
diff --git a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
index f69463a8..f5ef839c 100644
--- a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
+++ b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
@@ -59,13 +59,27 @@ public bool RunStarted(WizardRunParameters wizardRunParameters)
static void AddPackageToReplacementDictionary(WizardRunParameters wizardRunParameters, string name, string version, int packageIndex)
{
- string packagename = "packagerefname";
- string packageversion = "packagerefversion";
- string packagehasvalue = "packagerefhasvalue";
-
- wizardRunParameters.ReplacementsDictionary.Add($"${packagehasvalue}{packageIndex}$", true.ToString(CultureInfo.InvariantCulture));
- wizardRunParameters.ReplacementsDictionary.Add($"${packagename}{packageIndex}$", name);
- wizardRunParameters.ReplacementsDictionary.Add($"${packageversion}{packageIndex}$", version);
+ var refText = $"";
+ const string key = "$foo$";
+ if (wizardRunParameters.ReplacementsDictionary.TryGetValue(key, out string existingValue))
+ {
+ wizardRunParameters.ReplacementsDictionary[key] =
+ existingValue +
+ "\r\n " +
+ refText;
+ }
+ else
+ {
+ wizardRunParameters.ReplacementsDictionary.Add(key, refText);
+ }
+
+ //string packagename = "packagerefname";
+ //string packageversion = "packagerefversion";
+ //string packagehasvalue = "packagerefhasvalue";
+
+ //wizardRunParameters.ReplacementsDictionary.Add($"${packagehasvalue}{packageIndex}$", true.ToString(CultureInfo.InvariantCulture));
+ //wizardRunParameters.ReplacementsDictionary.Add($"${packagename}{packageIndex}$", name);
+ //wizardRunParameters.ReplacementsDictionary.Add($"${packageversion}{packageIndex}$", version);
}
}
}
From b3faa4bb88121dfc51e90357b38662f0f6f13ca0 Mon Sep 17 00:00:00 2001
From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com>
Date: Fri, 13 Jun 2025 09:58:23 -0500
Subject: [PATCH 06/14] Now fetching descriptors from URL. EnvironmentVariable
is available to override the URL for testing purposes. Added Description and
HyperLink content to the dialog box.
---
.../ProjectTemplate.csproj | 15 +-
.../Dialogs/AddNewReqnrollProjectDialog.xaml | 365 +++++++++---------
.../AddNewReqnrollProjectDialog.xaml.cs | 10 +-
.../Resources/TestFrameworkDescriptors.json | 168 +++++---
.../AddNewReqnrollProjectViewModel.cs | 83 +++-
.../Infrastructure/HttpClientWrapper.cs | 15 +
.../Wizards/Infrastructure/IHttpClient.cs | 5 +
.../Infrastructure/NewProjectMetaData.cs | 26 ++
.../NewProjectMetaDataProvider.cs | 97 ++---
.../Infrastructure/NewProjectModels.cs | 28 ++
.../Wizards/ReqnrollProjectWizard.cs | 32 +-
11 files changed, 510 insertions(+), 334 deletions(-)
create mode 100644 Reqnroll.VisualStudio/Wizards/Infrastructure/HttpClientWrapper.cs
create mode 100644 Reqnroll.VisualStudio/Wizards/Infrastructure/IHttpClient.cs
create mode 100644 Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaData.cs
create mode 100644 Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectModels.cs
diff --git a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
index 04ce7696..4dd793d7 100644
--- a/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
+++ b/Reqnroll.VisualStudio.ProjectTemplate/ProjectTemplate.csproj
@@ -6,21 +6,8 @@
enable
- $if$ ('$packagerefhasvalue0$' == 'True')
- $endif$$if$ ('$packagerefhasvalue1$' == 'True')
- $endif$$if$ ('$packagerefhasvalue2$' == 'True')
- $endif$$if$ ('$packagerefhasvalue3$' == 'True')
- $endif$$if$ ('$packagerefhasvalue4$' == 'True')
- $endif$$if$ ('$packagerefhasvalue5$' == 'True')
- $endif$$if$ ('$packagerefhasvalue6$' == 'True')
- $endif$$if$ ('$packagerefhasvalue7$' == 'True')
- $endif$$if$ ('$packagerefhasvalue8$' == 'True')
- $endif$$if$ ('$packagerefhasvalue9$' == 'True')
- $endif$
-
-
- $foo$
+ $nugetpackagereferences$
diff --git a/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml b/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml
index db226e1a..78938961 100644
--- a/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml
+++ b/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml
@@ -18,172 +18,173 @@
AllowDrop="True"
TextOptions.TextFormattingMode="Display"
HasDialogFrame="False" WindowStyle="None">
-
-
+
-
-
-
-
-
-
-
-
+
-
+
-
+
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- .NET Framework 4.6.2
- .NET Framework 4.7
- .NET Framework 4.7.1
- .NET Framework 4.7.2
- .NET Framework 4.8
- .NET Framework 4.8.1
- .NET 6.0
- .NET 7.0
- .NET 8.0
- .NET 9.0
-
-
+
+
+
+
-
-
-
+
+
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
+
+
+
diff --git a/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml.cs b/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml.cs
index 4efa5c1a..53c1a0c5 100644
--- a/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml.cs
+++ b/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml.cs
@@ -1,8 +1,9 @@
#nullable disable
-using System.Windows;
-using System.Windows.Controls;
using Microsoft.VisualStudio.Shell.Interop;
using Reqnroll.VisualStudio.UI.ViewModels;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Navigation;
namespace Reqnroll.VisualStudio.UI.Dialogs;
@@ -37,4 +38,9 @@ private void TestFramework_SelectionChanged(object sender, SelectionChangedEvent
ViewModel.UnitTestFramework = e.AddedItems[0].ToString();
e.Handled = true;
}
+
+ private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
+ {
+ OnLinkClicked(sender, e);
+ }
}
diff --git a/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json b/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
index f3c09490..703622fb 100644
--- a/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
+++ b/Reqnroll.VisualStudio/Resources/TestFrameworkDescriptors.json
@@ -1,53 +1,117 @@
{
- "xUnit": {
- "description": "Use xUnit v2 test executor with Reqnroll",
- "dependencies": [
- {
- "name": "Reqnroll.xUnit",
- "version": "2.4.1"
- },
- {
- "name": "xunit",
- "version": "2.8.1"
- },
- {
- "name": "xunit.runner.visualstudio",
- "version": "2.8.1"
- }
- ]
- },
- "MsTest": {
- "description": "Use MsTest v3 test executor with Reqnroll",
- "dependencies": [
- {
- "name": "Reqnroll.MsTest",
- "version": "2.4.1"
- },
- {
- "name": "MSTest.TestFramework",
- "version": "3.4.3"
- },
- {
- "name": "MSTest.TestAdapter",
- "version": "3.4.3"
- }
- ]
- },
- "NUnit": {
- "description": "Use NUnit v3 test executor with Reqnroll",
- "dependencies": [
- {
- "name": "Reqnroll.NUnit",
- "version": "2.4.1"
- },
- {
- "name": "nunit",
- "version": "3.14.0"
- },
- {
- "name": "NUnit3TestAdapter",
- "version": "4.5.0"
- }
- ]
- }
-}
+ "testFrameworks": [
+ {
+ "label": "xUnit",
+ "description": "Use xUnit v2 test executor with Reqnroll",
+ "url": "https://xunit.net",
+ "dependencies": [
+ {
+ "name": "Reqnroll.xUnit",
+ "version": "2.4.1"
+ },
+ {
+ "name": "xunit",
+ "version": "2.8.1"
+ },
+ {
+ "name": "xunit.runner.visualstudio",
+ "version": "2.8.1"
+ }
+ ]
+ },
+ {
+ "label": "MsTest",
+ "description": "Use MsTest v3 test executor with Reqnroll",
+ "url": "https://github.com/microsoft/testfx?tab=readme-ov-file",
+ "dependencies": [
+ {
+ "name": "Reqnroll.MsTest",
+ "version": "2.4.1"
+ },
+ {
+ "name": "MSTest.TestFramework",
+ "version": "3.4.3"
+ },
+ {
+ "name": "MSTest.TestAdapter",
+ "version": "3.4.3"
+ }
+ ]
+ },
+ {
+ "label": "NUnit",
+ "description": "Use NUnit v3 test executor with Reqnroll",
+ "url": "https://nunit.org",
+ "dependencies": [
+ {
+ "name": "Reqnroll.NUnit",
+ "version": "2.4.1"
+ },
+ {
+ "name": "nunit",
+ "version": "3.14.0"
+ },
+ {
+ "name": "NUnit3TestAdapter",
+ "version": "4.5.0"
+ }
+ ]
+ }
+ ],
+ "dotNetFrameworks": [
+ {
+ "tag": "net462",
+ "label": ".NET Framework 4.6.2"
+ },
+ {
+ "tag": "net47",
+ "label": ".NET Framework 4.7"
+ },
+ {
+ "tag": "net471",
+ "label": ".NET Framework 4.7.1"
+ },
+ {
+ "tag": "net472",
+ "label": ".NET Framework 4.7.2"
+ },
+ {
+ "tag": "net48",
+ "label": ".NET Framework 4.8"
+ },
+ {
+ "tag": "net48",
+ "label": ".NET Framework 4.8.1"
+ },
+ {
+ "tag": "net6.0",
+ "label": ".NET 6.0"
+ },
+ {
+ "tag": "net7.0",
+ "label": ".NET 7.0"
+ },
+ {
+ "tag": "net8.0",
+ "label": ".NET 8.0",
+ "default": true
+ },
+ {
+ "tag": "net9.0",
+ "label": ".NET 9.0"
+ }
+ ],
+ "validationFrameworks": [
+ {
+ "label": "FluentAssertions",
+ "description": "Use Fluent Assertions library with Reqnroll",
+ "url": "https://fluentassertions.com",
+ "dependencies": [
+ {
+ "name": "FluentAssertions",
+ "version": "8.3.0"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs b/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs
index 0638344f..d7a233cf 100644
--- a/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs
+++ b/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs
@@ -1,26 +1,55 @@
#nullable disable
+using Newtonsoft.Json.Linq;
+using Reqnroll.VisualStudio.Wizards.Infrastructure;
+
namespace Reqnroll.VisualStudio.UI.ViewModels;
public class AddNewReqnrollProjectViewModel : INotifyPropertyChanged
{
private const string MsTest = "MsTest";
- private const string Net8 = "net8.0";
+ private const string Net8Tag = "net8.0";
+ private const string Net8Label = ".NET 8.0";
+ private IDictionary DotNetFrameworkLabelToTagMap = new Dictionary();
+ private IDictionary TestFrameworkMetaData;
public AddNewReqnrollProjectViewModel() { }
- public AddNewReqnrollProjectViewModel(IEnumerable testFrameworkNames)
+ public AddNewReqnrollProjectViewModel(INewProjectMetaDataProvider metaDataProvider)
{
- TestFrameworks = new (testFrameworkNames);
+ metaDataProvider.RetrieveNewProjectMetaData(
+ (NewProjectMetaData md) =>
+ {
+ DotNetFrameworkLabelToTagMap = md.DotNetFrameworkNameToTagMap;
+ DotNetFrameworks = new(md.DotNetFrameworks);
+ DotNetFramework = md.DotNetFrameworkDefault;
+ TestFrameworks = new(md.TestFrameworks);
+ UnitTestFramework = md.TestFrameworkDefault;
+ TestFrameworkMetaData = md.TestFrameworkMetaData;
+ });
}
#if DEBUG
public static AddNewReqnrollProjectViewModel DesignData = new()
{
- DotNetFramework = Net8,
+ DotNetFramework = Net8Label,
UnitTestFramework = MsTest,
FluentAssertionsIncluded = false
};
#endif
- private string _dotNetFramework = Net8;
+
+ // The tag value of the currently selected .NET framework (ie, "net8.0")
+ public string DotNetFrameworkTag
+ {
+ get
+ {
+ return DotNetFrameworkLabelToTagMap.Count > 0 ? DotNetFrameworkLabelToTagMap[DotNetFramework] : Net8Tag;
+ }
+ }
+ private string _dotNetFramework = Net8Label;
+ private string _unitTestFramework = MsTest;
+
+ #region XAML Bound Properties
+
+ // The currently selected DotNetFramework label (ie, ".NET 8.0")
public string DotNetFramework
{
get => _dotNetFramework;
@@ -30,13 +59,53 @@ public string DotNetFramework
OnPropertyChanged(nameof(TestFrameworks));
}
}
+ // The list of .NET Frameworks that appear in the combobox for selection by the user
+ public ObservableCollection DotNetFrameworks { get; set; } = new(new List { ".NET Framework 4.8.1", ".NET 8.0" });
+
+ // The currently selected Unit Test Framework (ie, "xUnit")
+ public string UnitTestFramework
+ {
+ get => _unitTestFramework;
+ set
+ {
+ _unitTestFramework = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(UnitTestFrameworkDescription));
+ OnPropertyChanged(nameof(UnitTestFrameworkUrl));
+ }
+ }
+
+ // The list of available Unit Test Framework that appear in a combobox for selection by the user
+ public ObservableCollection TestFrameworks { get; set; } = new(new List { "MSTest", "NUnit", "xUnit" });
+
+ // A line of text describing the currently selected Unit Test Framework (bound to a Textbox)
+ public string UnitTestFrameworkDescription
+ {
+ get
+ {
+ if (TestFrameworkMetaData.TryGetValue(_unitTestFramework, out var frameworkInfo))
+ return frameworkInfo.Description;
+ return "";
+ }
+ }
+
+ // URL to the home page of the currently selected Unit Test Framework (bound to a TextBox)
+ public string UnitTestFrameworkUrl
+ {
+ get
+ {
+ if (TestFrameworkMetaData.TryGetValue(_unitTestFramework, out var frameworkInfo))
+ return frameworkInfo.Url;
+ return "";
+ }
+ }
- public string UnitTestFramework { get; set; } = MsTest;
// FluentAssertions suggestion is temporarily hidden from the UI as it is not free for commercial use anymore.
// See https://xceed.com/fluent-assertions-faq/
// Maybe we could consider suggesting https://github.com/shouldly/shouldly instead.
public bool FluentAssertionsIncluded { get; set; } = false;
- public ObservableCollection TestFrameworks { get; } = new(new List { "MSTest", "NUnit", "xUnit" });
+
+ #endregion
public event PropertyChangedEventHandler PropertyChanged;
diff --git a/Reqnroll.VisualStudio/Wizards/Infrastructure/HttpClientWrapper.cs b/Reqnroll.VisualStudio/Wizards/Infrastructure/HttpClientWrapper.cs
new file mode 100644
index 00000000..3c1efd08
--- /dev/null
+++ b/Reqnroll.VisualStudio/Wizards/Infrastructure/HttpClientWrapper.cs
@@ -0,0 +1,15 @@
+namespace Reqnroll.VisualStudio.Wizards.Infrastructure;
+
+[Export(typeof(IHttpClient))]
+public class HttpClientWrapper : IHttpClient
+{
+ public async Task GetStringAsync(string url, CancellationTokenSource cts)
+ {
+ using (var client = new System.Net.Http.HttpClient())
+ using (var response = await client.GetAsync(url, cts.Token))
+ {
+ response.EnsureSuccessStatusCode();
+ return await response.Content.ReadAsStringAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Reqnroll.VisualStudio/Wizards/Infrastructure/IHttpClient.cs b/Reqnroll.VisualStudio/Wizards/Infrastructure/IHttpClient.cs
new file mode 100644
index 00000000..3ba6ded2
--- /dev/null
+++ b/Reqnroll.VisualStudio/Wizards/Infrastructure/IHttpClient.cs
@@ -0,0 +1,5 @@
+namespace Reqnroll.VisualStudio.Wizards.Infrastructure;
+public interface IHttpClient
+{
+ Task GetStringAsync(string url, CancellationTokenSource cts);
+}
\ No newline at end of file
diff --git a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaData.cs b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaData.cs
new file mode 100644
index 00000000..6980e95d
--- /dev/null
+++ b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaData.cs
@@ -0,0 +1,26 @@
+using System.Linq;
+
+namespace Reqnroll.VisualStudio.Wizards.Infrastructure
+{
+ public class NewProjectMetaData
+ {
+ public readonly IEnumerable TestFrameworks;
+ public readonly IEnumerable DotNetFrameworks;
+ public readonly string TestFrameworkDefault;
+ public readonly string DotNetFrameworkDefault;
+ public readonly IDictionary DotNetFrameworkNameToTagMap;
+ public readonly IDictionary TestFrameworkMetaData;
+
+ public NewProjectMetaData(NewProjectMetaRecord retrievedData)
+ {
+ TestFrameworks = retrievedData.TestFrameworks.Select(tf => tf.Label).ToList();
+ DotNetFrameworks = retrievedData.DotNetFrameworks.Select(dn => dn.Label).ToList();
+ TestFrameworkDefault = TestFrameworks.First();
+ DotNetFrameworkDefault = retrievedData.DotNetFrameworks.First(dn => dn.Default == true).Label;
+ DotNetFrameworkNameToTagMap = retrievedData.DotNetFrameworks
+ .ToDictionary(d => d.Label, d => d.Tag);
+ TestFrameworkMetaData = retrievedData.TestFrameworks
+ .ToDictionary(tf => tf.Label, tf => tf);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
index aa6d0540..f8d9fd0d 100644
--- a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
+++ b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
@@ -2,88 +2,67 @@ namespace Reqnroll.VisualStudio.Wizards.Infrastructure;
public interface INewProjectMetaDataProvider
{
- IEnumerable TestFrameworks { get; }
IEnumerable DependenciesOf(string testFramework);
+ void RetrieveNewProjectMetaData(Action onRetrieved);
}
-public record NugetPackageDescriptor(string name, string version);
-public record TestFrameworkInfoModel(string description, IEnumerable dependencies);
[Export(typeof(INewProjectMetaDataProvider))]
public class NewProjectMetaDataProvider : INewProjectMetaDataProvider
{
- private Dictionary _fallbackTestFrameworkDescriptors = new();
- private Task> _httpTask;
- private Lazy> _resolvedTestFrameworkDescriptors;
+ private const string environmentVariableOverrideOfMetaDataEndpointURL = "REQNROLL_VISUALSTUDIOEXTENSION_NPW_FRAMEWORKMETADATAENDPOINTURL";
+ private const string _metaDataEndpointUrl = "https://assets.reqnroll.net/testframeworkmetadata/testframeworks.json";
+ private NewProjectMetaRecord _retrievedData;
+ private readonly IHttpClient _httpClient;
[ImportingConstructor]
- public NewProjectMetaDataProvider()
+ public NewProjectMetaDataProvider(IHttpClient httpClient)
{
- // read static metadata from a resource file, deserialize the resulting json, storing it in the _fallbackTestFrameworkDescriptors field
- var resourceName = "Reqnroll.VisualStudio.Resources.TestFrameworkDescriptors.json";
- using (var stream = typeof(NewProjectMetaDataProvider).Assembly.GetManifestResourceStream(resourceName))
- using (var reader = new StreamReader(stream))
- {
- var json = reader.ReadToEnd();
- var data = JsonSerialization.DeserializeObject>(json);
- if (data != null)
- _fallbackTestFrameworkDescriptors = data;
- }
-
- // launch a task to read metadata from http resource and deserialize the resulting json, save the Task to a field
- _httpTask = Task.Run(async () =>
- {
- try
- {
- using (var httpClient = new System.Net.Http.HttpClient())
- {
- var httpJson = await httpClient.GetStringAsync("https://example.com/testframeworks.json").ConfigureAwait(false);
- var httpData = JsonSerialization.DeserializeObject>(httpJson);
- return httpData;
- }
- }
- catch
- {
- return null;
- }
- });
+ _httpClient = httpClient;
+ }
- _resolvedTestFrameworkDescriptors = new Lazy>(FinishRetrievalOfTestFrameworkDescriptors);
+ public void RetrieveNewProjectMetaData(Action onRetrievedAction)
+ {
+ _retrievedData = FetchDescriptorsFromReqnrollWebsite(_httpClient);
+ var md = new NewProjectMetaData(_retrievedData);
+ onRetrievedAction(md);
}
- public IEnumerable TestFrameworks
+ private NewProjectMetaRecord FetchDescriptorsFromReqnrollWebsite(IHttpClient httpClient)
{
- get
+ try
+ {
+ using (var cts = new DebuggableCancellationTokenSource(TimeSpan.FromSeconds(10)))
+ {
+ var overrideUrl = Environment.GetEnvironmentVariable(environmentVariableOverrideOfMetaDataEndpointURL);
+ var url = overrideUrl ?? _metaDataEndpointUrl;
+ var httpJson = Task.Run(() => httpClient.GetStringAsync(url, cts)).Result;
+ var httpData = JsonSerialization.DeserializeObject(httpJson);
+ return httpData ?? CreateFallBackMetaData();
+ }
+ }
+ catch
{
- var descriptors = _resolvedTestFrameworkDescriptors.Value;
- return descriptors.Keys;
+ return CreateFallBackMetaData();
}
}
public IEnumerable DependenciesOf(string testFramework)
{
- var descriptors = _resolvedTestFrameworkDescriptors.Value;
- return descriptors[testFramework].dependencies;
+ var dependencies = _retrievedData.TestFrameworks.First(tf => tf.Label == testFramework).Dependencies;
+ return dependencies;
}
- private Dictionary FinishRetrievalOfTestFrameworkDescriptors()
+ internal virtual NewProjectMetaRecord CreateFallBackMetaData()
{
- // Wait for the HTTP task to complete
- if (_httpTask == null)
- return _fallbackTestFrameworkDescriptors;
-
- try
- {
- var result = _httpTask.GetAwaiter().GetResult();
- if (result != null)
- {
- return result;
- }
- }
- catch
+ // read static metadata from a resource file, deserialize the resulting json, storing it in the _fallbackTestFrameworkDescriptors field
+ var resourceName = "Reqnroll.VisualStudio.Resources.TestFrameworkDescriptors.json";
+ using (var stream = typeof(NewProjectMetaDataProvider).Assembly.GetManifestResourceStream(resourceName))
+ using (var reader = new StreamReader(stream))
{
- // Ignore exceptions, keep existing _fallbackTestFrameworkDescriptors
+ var json = reader.ReadToEnd();
+ var data = JsonSerialization.DeserializeObject(json);
+ return data;
}
- // If we've gotten to this point, the http end point returned no result or failed. So, we fall back.
- return _fallbackTestFrameworkDescriptors;
}
+
}
diff --git a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectModels.cs b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectModels.cs
new file mode 100644
index 00000000..5875d85e
--- /dev/null
+++ b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectModels.cs
@@ -0,0 +1,28 @@
+namespace Reqnroll.VisualStudio.Wizards.Infrastructure;
+
+// Root object
+public record NewProjectMetaRecord
+{
+ public List TestFrameworks { get; init; }
+ public List DotNetFrameworks { get; init; }
+ public List? ValidationFrameworks { get; init; }
+}
+
+// Describes a framework. Used to describe both Testing Frameworks(such as Nunit) and accessory frameworks (eg, Validation frameworks like FluentAssertions)
+public record FrameworkInfo
+{
+ public string Label { get; init; }
+ public string Description { get; init; }
+ public string Url { get; init; }
+ public List Dependencies { get; init; }
+}
+
+// Framework information
+public record DotNetFrameworkInfo
+{
+ public string Tag { get; init; }
+ public string Label { get; init; }
+ public bool Default { get; init; }
+}
+
+public record NugetPackageDescriptor(string name, string version);
diff --git a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
index f5ef839c..6475a28d 100644
--- a/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
+++ b/Reqnroll.VisualStudio/Wizards/ReqnrollProjectWizard.cs
@@ -1,7 +1,8 @@
+using Reqnroll.VisualStudio.ProjectSystem;
+using Reqnroll.VisualStudio.Wizards.Infrastructure;
using System;
using System.Globalization;
using System.Linq;
-using Reqnroll.VisualStudio.Wizards.Infrastructure;
namespace Reqnroll.VisualStudio.Wizards;
@@ -24,43 +25,38 @@ public bool RunStarted(WizardRunParameters wizardRunParameters)
{
_monitoringService.MonitorProjectTemplateWizardStarted();
- var frameworkNames = _newProjectMetaDataProvider.TestFrameworks;
-
- var viewModel = new AddNewReqnrollProjectViewModel(frameworkNames);
+ var viewModel = new AddNewReqnrollProjectViewModel(_newProjectMetaDataProvider);
var dialogResult = _deveroomWindowManager.ShowDialog(viewModel);
if (!dialogResult.HasValue || !dialogResult.Value) return false;
- _monitoringService.MonitorProjectTemplateWizardCompleted(viewModel.DotNetFramework, viewModel.UnitTestFramework,
+ _monitoringService.MonitorProjectTemplateWizardCompleted(viewModel.DotNetFrameworkTag, viewModel.UnitTestFramework,
viewModel.FluentAssertionsIncluded);
- int packageIndex = 0;
-
// insert set of replacement variables for the SDK package
- AddPackageToReplacementDictionary(wizardRunParameters, "Microsoft.NET.Test.Sdk", "17.10.0", packageIndex);
+ AddPackageToReplacementDictionary(wizardRunParameters, "Microsoft.NET.Test.Sdk", "17.10.0");
var dependencies = _newProjectMetaDataProvider.DependenciesOf(viewModel.UnitTestFramework);
foreach (var package in dependencies)
{
- packageIndex++;
var name = package.name;
var version = package.version;
- AddPackageToReplacementDictionary(wizardRunParameters, name, version, packageIndex);
+ AddPackageToReplacementDictionary(wizardRunParameters, name, version);
}
if (viewModel.FluentAssertionsIncluded)
- AddPackageToReplacementDictionary(wizardRunParameters, "FluentAssertions", "6.12.0", packageIndex+1);
+ AddPackageToReplacementDictionary(wizardRunParameters, "FluentAssertions", "6.12.0");
- wizardRunParameters.ReplacementsDictionary.Add("$dotnetframework$", viewModel.DotNetFramework);
+ wizardRunParameters.ReplacementsDictionary.Add("$dotnetframework$", viewModel.DotNetFrameworkTag);
wizardRunParameters.ReplacementsDictionary.Add("$fluentassertionsincluded$",
viewModel.FluentAssertionsIncluded.ToString(CultureInfo.InvariantCulture));
return true;
- static void AddPackageToReplacementDictionary(WizardRunParameters wizardRunParameters, string name, string version, int packageIndex)
+ static void AddPackageToReplacementDictionary(WizardRunParameters wizardRunParameters, string name, string version)
{
var refText = $"";
- const string key = "$foo$";
+ const string key = "$nugetpackagereferences$";
if (wizardRunParameters.ReplacementsDictionary.TryGetValue(key, out string existingValue))
{
wizardRunParameters.ReplacementsDictionary[key] =
@@ -72,14 +68,6 @@ static void AddPackageToReplacementDictionary(WizardRunParameters wizardRunParam
{
wizardRunParameters.ReplacementsDictionary.Add(key, refText);
}
-
- //string packagename = "packagerefname";
- //string packageversion = "packagerefversion";
- //string packagehasvalue = "packagerefhasvalue";
-
- //wizardRunParameters.ReplacementsDictionary.Add($"${packagehasvalue}{packageIndex}$", true.ToString(CultureInfo.InvariantCulture));
- //wizardRunParameters.ReplacementsDictionary.Add($"${packagename}{packageIndex}$", name);
- //wizardRunParameters.ReplacementsDictionary.Add($"${packageversion}{packageIndex}$", version);
}
}
}
From 2175445bde673a0cb2dd075a2be7b6aceb7d8f46 Mon Sep 17 00:00:00 2001
From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com>
Date: Fri, 13 Jun 2025 12:07:33 -0500
Subject: [PATCH 07/14] Added Unit Tests of the NewProjectMetaDataProvider.
Added an abstraction for Env Variable access to facilitate testing.
---
.../Infrastructure/EnvironmentWrapper.cs | 16 ++
...tModels.cs => NewProjectMetaDataModels.cs} | 0
.../NewProjectMetaDataProvider.cs | 53 ++--
.../NewProjectMetaDataProviderTests.cs | 254 ++++++++++++++++++
4 files changed, 307 insertions(+), 16 deletions(-)
create mode 100644 Reqnroll.VisualStudio/Wizards/Infrastructure/EnvironmentWrapper.cs
rename Reqnroll.VisualStudio/Wizards/Infrastructure/{NewProjectModels.cs => NewProjectMetaDataModels.cs} (100%)
create mode 100644 Tests/Reqnroll.VisualStudio.Tests/Wizards/Infrastructure/NewProjectMetaDataProviderTests.cs
diff --git a/Reqnroll.VisualStudio/Wizards/Infrastructure/EnvironmentWrapper.cs b/Reqnroll.VisualStudio/Wizards/Infrastructure/EnvironmentWrapper.cs
new file mode 100644
index 00000000..8234daad
--- /dev/null
+++ b/Reqnroll.VisualStudio/Wizards/Infrastructure/EnvironmentWrapper.cs
@@ -0,0 +1,16 @@
+namespace Reqnroll.VisualStudio.Wizards.Infrastructure
+{
+ public interface IEnvironmentWrapper
+ {
+ string GetEnvironmentVariable(string name);
+ }
+
+ [Export(typeof(IEnvironmentWrapper))]
+ public class EnvironmentWrapper : IEnvironmentWrapper
+ {
+ public string GetEnvironmentVariable(string name)
+ {
+ return Environment.GetEnvironmentVariable(name);
+ }
+ }
+}
diff --git a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectModels.cs b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataModels.cs
similarity index 100%
rename from Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectModels.cs
rename to Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataModels.cs
diff --git a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
index f8d9fd0d..97fd4246 100644
--- a/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
+++ b/Reqnroll.VisualStudio/Wizards/Infrastructure/NewProjectMetaDataProvider.cs
@@ -11,29 +11,31 @@ public class NewProjectMetaDataProvider : INewProjectMetaDataProvider
{
private const string environmentVariableOverrideOfMetaDataEndpointURL = "REQNROLL_VISUALSTUDIOEXTENSION_NPW_FRAMEWORKMETADATAENDPOINTURL";
private const string _metaDataEndpointUrl = "https://assets.reqnroll.net/testframeworkmetadata/testframeworks.json";
- private NewProjectMetaRecord _retrievedData;
+ private NewProjectMetaData? _metadata;
private readonly IHttpClient _httpClient;
+ private readonly IEnvironmentWrapper _environmentWrapper;
[ImportingConstructor]
- public NewProjectMetaDataProvider(IHttpClient httpClient)
+ public NewProjectMetaDataProvider(IHttpClient httpClient, IEnvironmentWrapper environmentWrapper)
{
_httpClient = httpClient;
+ _environmentWrapper = environmentWrapper;
}
public void RetrieveNewProjectMetaData(Action onRetrievedAction)
{
- _retrievedData = FetchDescriptorsFromReqnrollWebsite(_httpClient);
- var md = new NewProjectMetaData(_retrievedData);
- onRetrievedAction(md);
+ var retrievedData = FetchDescriptorsFromReqnrollWebsite(_httpClient);
+ _metadata = new NewProjectMetaData(retrievedData);
+ onRetrievedAction(_metadata);
}
- private NewProjectMetaRecord FetchDescriptorsFromReqnrollWebsite(IHttpClient httpClient)
+ internal NewProjectMetaRecord FetchDescriptorsFromReqnrollWebsite(IHttpClient httpClient)
{
try
{
using (var cts = new DebuggableCancellationTokenSource(TimeSpan.FromSeconds(10)))
{
- var overrideUrl = Environment.GetEnvironmentVariable(environmentVariableOverrideOfMetaDataEndpointURL);
+ var overrideUrl = _environmentWrapper.GetEnvironmentVariable(environmentVariableOverrideOfMetaDataEndpointURL);
var url = overrideUrl ?? _metaDataEndpointUrl;
var httpJson = Task.Run(() => httpClient.GetStringAsync(url, cts)).Result;
var httpData = JsonSerialization.DeserializeObject(httpJson);
@@ -48,21 +50,40 @@ private NewProjectMetaRecord FetchDescriptorsFromReqnrollWebsite(IHttpClient htt
public IEnumerable DependenciesOf(string testFramework)
{
- var dependencies = _retrievedData.TestFrameworks.First(tf => tf.Label == testFramework).Dependencies;
+ IEnumerable dependencies = Enumerable.Empty();
+ if (_metadata != null && _metadata.TestFrameworkMetaData.TryGetValue(testFramework, out var framework))
+ {
+ dependencies = framework.Dependencies;
+ }
return dependencies;
}
internal virtual NewProjectMetaRecord CreateFallBackMetaData()
{
- // read static metadata from a resource file, deserialize the resulting json, storing it in the _fallbackTestFrameworkDescriptors field
- var resourceName = "Reqnroll.VisualStudio.Resources.TestFrameworkDescriptors.json";
- using (var stream = typeof(NewProjectMetaDataProvider).Assembly.GetManifestResourceStream(resourceName))
- using (var reader = new StreamReader(stream))
+ try
{
- var json = reader.ReadToEnd();
- var data = JsonSerialization.DeserializeObject(json);
- return data;
+ // read static metadata from a resource file, deserialize the resulting json
+ var resourceName = "Reqnroll.VisualStudio.Resources.TestFrameworkDescriptors.json";
+ var assembly = typeof(NewProjectMetaDataProvider).Assembly;
+ using (var stream = assembly.GetManifestResourceStream(resourceName))
+ {
+ if (stream == null)
+ {
+ return null; // Resource not found
+ }
+
+ using (var reader = new StreamReader(stream))
+ {
+ var json = reader.ReadToEnd();
+ var data = JsonSerialization.DeserializeObject(json);
+ return data; // Could be null if deserialization fails
+ }
+ }
+ }
+ catch
+ {
+ // Any exception during resource reading or deserialization
+ return null;
}
}
-
}
diff --git a/Tests/Reqnroll.VisualStudio.Tests/Wizards/Infrastructure/NewProjectMetaDataProviderTests.cs b/Tests/Reqnroll.VisualStudio.Tests/Wizards/Infrastructure/NewProjectMetaDataProviderTests.cs
new file mode 100644
index 00000000..edf41209
--- /dev/null
+++ b/Tests/Reqnroll.VisualStudio.Tests/Wizards/Infrastructure/NewProjectMetaDataProviderTests.cs
@@ -0,0 +1,254 @@
+using FluentAssertions;
+using Moq;
+using Reqnroll.VisualStudio.Wizards.Infrastructure;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Reqnroll.VisualStudio.Tests.Wizards.Infrastructure
+{
+ class StubNewProjectDataProvider : NewProjectMetaDataProvider
+ {
+ private NewProjectMetaRecord _stubData;
+
+ internal StubNewProjectDataProvider(IHttpClient httpClient, IEnvironmentWrapper environmentWrapper, NewProjectMetaRecord stubData)
+ : base(httpClient, environmentWrapper)
+ {
+ _stubData = stubData;
+ }
+ internal override NewProjectMetaRecord CreateFallBackMetaData()
+ {
+ return _stubData;
+ }
+ }
+ public class NewProjectMetaDataProviderTests
+ {
+ private readonly Mock _httpClientMock;
+ private readonly Mock _environmentWrapperMock;
+ private readonly NewProjectMetaDataProvider _sut;
+
+ public NewProjectMetaDataProviderTests()
+ {
+ _httpClientMock = new Mock();
+ _environmentWrapperMock = new Mock();
+
+ _sut = new NewProjectMetaDataProvider(_httpClientMock.Object, _environmentWrapperMock.Object);
+ }
+
+ [Fact]
+ public void RetrieveNewProjectMetaData_InvokesCallback_WithMetadata()
+ {
+ // Arrange
+ var validJson = CreateValidMetadataJson();
+ _httpClientMock
+ .Setup(x => x.GetStringAsync(It.IsAny(), It.IsAny()))
+ .Returns(Task.FromResult(validJson));
+
+ NewProjectMetaData? receivedMetadata = null;
+ Action callback = md => receivedMetadata = md;
+
+ // Act
+ _sut.RetrieveNewProjectMetaData(callback);
+
+ // Assert
+ receivedMetadata.Should().NotBeNull();
+ receivedMetadata!.TestFrameworks.Should().Contain("NUnit");
+ }
+
+ [Fact]
+ public void FetchDescriptorsFromReqnrollWebsite_UsesDefaultUrl_WhenNoOverride()
+ {
+ // Arrange
+ var validJson = CreateValidMetadataJson();
+ _environmentWrapperMock
+ .Setup(x => x.GetEnvironmentVariable(It.IsAny()))
+ .Returns((string)null);
+
+ _httpClientMock
+ .Setup(x => x.GetStringAsync("https://assets.reqnroll.net/testframeworkmetadata/testframeworks.json", It.IsAny()))
+ .Returns(Task.FromResult(validJson));
+
+ // Act
+ var result = _sut.FetchDescriptorsFromReqnrollWebsite(_httpClientMock.Object);
+
+ // Assert
+ result.Should().NotBeNull();
+ _httpClientMock.Verify(
+ x => x.GetStringAsync("https://assets.reqnroll.net/testframeworkmetadata/testframeworks.json", It.IsAny()),
+ Times.Once);
+ }
+
+ [Fact]
+ public void FetchDescriptorsFromReqnrollWebsite_UsesOverrideUrl_WhenSpecified()
+ {
+ // Arrange
+ var customUrl = "https://custom-url/metadata.json";
+ var validJson = CreateValidMetadataJson();
+
+ _environmentWrapperMock
+ .Setup(x => x.GetEnvironmentVariable("REQNROLL_VISUALSTUDIOEXTENSION_NPW_FRAMEWORKMETADATAENDPOINTURL"))
+ .Returns(customUrl);
+
+ _httpClientMock
+ .Setup(x => x.GetStringAsync(customUrl, It.IsAny()))
+ .Returns(Task.FromResult(validJson));
+
+ // Act
+ var result = _sut.FetchDescriptorsFromReqnrollWebsite(_httpClientMock.Object);
+
+ // Assert
+ result.Should().NotBeNull();
+ _httpClientMock.Verify(
+ x => x.GetStringAsync(customUrl, It.IsAny()),
+ Times.Once);
+ }
+
+ [Fact]
+ public void FetchDescriptorsFromReqnrollWebsite_UsesFallback_WhenHttpRequestFails()
+ {
+ // Arrange
+ _httpClientMock
+ .Setup(x => x.GetStringAsync(It.IsAny(), It.IsAny()))
+ .Throws(new Exception("Connection error"));
+
+
+ var fallbackData = new NewProjectMetaRecord
+ {
+ TestFrameworks = new List { new FrameworkInfo { Label = "FallbackFramework" } }
+ };
+
+ var sutWithFallbackMock = new StubNewProjectDataProvider(
+ _httpClientMock.Object, _environmentWrapperMock.Object, fallbackData);
+
+
+ // Act
+ var result = sutWithFallbackMock.FetchDescriptorsFromReqnrollWebsite(_httpClientMock.Object);
+
+ // Assert
+ result.Should().BeSameAs(fallbackData);
+ }
+
+ [Fact]
+ public void FetchDescriptorsFromReqnrollWebsite_UsesFallback_WhenDeserializationFails()
+ {
+ // Arrange
+ _httpClientMock
+ .Setup(x => x.GetStringAsync(It.IsAny(), It.IsAny()))
+ .Returns(Task.FromResult("{ invalid json }"));
+
+ var fallbackData = new NewProjectMetaRecord
+ {
+ TestFrameworks = new List { new FrameworkInfo { Label = "FallbackFramework" } }
+ };
+
+ var sutWithFallbackMock = new StubNewProjectDataProvider(
+ _httpClientMock.Object, _environmentWrapperMock.Object, fallbackData);
+
+ // Act
+ var result = sutWithFallbackMock.FetchDescriptorsFromReqnrollWebsite(_httpClientMock.Object);
+
+ // Assert
+ result.Should().BeSameAs(fallbackData);
+ }
+
+ [Fact]
+ public void DependenciesOf_ReturnsCorrectDependencies_WhenFrameworkExists()
+ {
+ // Arrange
+ var validJson = CreateValidMetadataJson();
+ _httpClientMock
+ .Setup(x => x.GetStringAsync(It.IsAny(), It.IsAny()))
+ .Returns(Task.FromResult(validJson));
+
+ _sut.RetrieveNewProjectMetaData(_ => { });
+
+ // Act
+ var dependencies = _sut.DependenciesOf("NUnit");
+
+ // Assert
+ dependencies.Should().NotBeEmpty();
+ dependencies.Should().Contain(d => d.name == "NUnit");
+ dependencies.Should().Contain(d => d.name == "NUnit3TestAdapter");
+ }
+
+ [Fact]
+ public void DependenciesOf_ReturnsEmptyCollection_WhenFrameworkDoesNotExist()
+ {
+ // Arrange
+ var validJson = CreateValidMetadataJson();
+ _httpClientMock
+ .Setup(x => x.GetStringAsync(It.IsAny(), It.IsAny()))
+ .Returns(Task.FromResult(validJson));
+
+ _sut.RetrieveNewProjectMetaData(_ => { });
+
+ // Act
+ var dependencies = _sut.DependenciesOf("NonExistentFramework");
+
+ // Assert
+ dependencies.Should().BeEmpty();
+ }
+
+ [Fact]
+ public void CreateFallBackMetaData_ReturnsValidMetaData_WhenResourceExists()
+ {
+ // Arrange
+ var provider = new NewProjectMetaDataProvider(_httpClientMock.Object, _environmentWrapperMock.Object);
+
+ // Act
+ var result = provider.CreateFallBackMetaData();
+
+ // Assert
+ result.Should().NotBeNull();
+ result.TestFrameworks.Should().NotBeEmpty();
+ }
+
+ private string CreateValidMetadataJson()
+ {
+ return @"{
+ ""testFrameworks"": [
+ {
+ ""label"": ""NUnit"",
+ ""description"": ""NUnit test framework"",
+ ""url"": ""https://nunit.org"",
+ ""dependencies"": [
+ {
+ ""name"": ""NUnit"",
+ ""version"": ""3.13.2""
+ },
+ {
+ ""name"": ""NUnit3TestAdapter"",
+ ""version"": ""4.0.0""
+ }
+ ]
+ },
+ {
+ ""label"": ""xUnit"",
+ ""description"": ""xUnit test framework"",
+ ""url"": ""https://xunit.net"",
+ ""dependencies"": [
+ {
+ ""name"": ""xunit"",
+ ""version"": ""2.4.1""
+ },
+ {
+ ""name"": ""xunit.runner.visualstudio"",
+ ""version"": ""2.4.3""
+ }
+ ]
+ }
+ ],
+ ""dotNetFrameworks"": [
+ {
+ ""label"": "".NET 6.0"",
+ ""tag"": ""net6.0"",
+ ""default"": ""true""
+ }
+ ]
+ }";
+ }
+ }
+}
\ No newline at end of file
From 2a0853d85153a83c411576b439f378c9727fccff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?=
Date: Mon, 16 Jun 2025 15:49:41 +0200
Subject: [PATCH 08/14] reset whitespace changes in
AddNewReqnrollProjectDialog.xaml for better diff
---
.../Dialogs/AddNewReqnrollProjectDialog.xaml | 334 +++++++++---------
1 file changed, 167 insertions(+), 167 deletions(-)
diff --git a/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml b/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml
index 78938961..28710173 100644
--- a/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml
+++ b/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml
@@ -18,173 +18,173 @@
AllowDrop="True"
TextOptions.TextFormattingMode="Display"
HasDialogFrame="False" WindowStyle="None">
-
-
+
-
-
-
-
-
-
-
-
+
-
+
-
+
-
+
+
+
+
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
-
-
-
+
+
-
+
-
-
-
+
+
-
-
+
+
@@ -259,25 +259,25 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
+
+
+
From 0c20e34d92bdd412ababa2df0d2ba53c93fec476 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?=
Date: Mon, 16 Jun 2025 16:11:27 +0200
Subject: [PATCH 09/14] Update DesignData AddNewReqnrollProjectViewModel for
Reqnroll.VisualStudio.UI.Tester
---
.../AddNewReqnrollProjectViewModel.cs | 39 ++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs b/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs
index d7a233cf..6f2663be 100644
--- a/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs
+++ b/Reqnroll.VisualStudio/UI/ViewModels/AddNewReqnrollProjectViewModel.cs
@@ -31,7 +31,44 @@ public AddNewReqnrollProjectViewModel(INewProjectMetaDataProvider metaDataProvid
{
DotNetFramework = Net8Label,
UnitTestFramework = MsTest,
- FluentAssertionsIncluded = false
+ FluentAssertionsIncluded = false,
+ TestFrameworkMetaData = new Dictionary()
+ {
+ {
+ MsTest, new FrameworkInfo()
+ {
+ Label = "MsTest Label",
+ Description = "MsTest description",
+ Url = "https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-csharp-with-mstest",
+ Dependencies = new List()
+ {
+ new("Reqnroll.MsTest", "2.4.1"),
+ new("MSTest.TestFramework", "3.9.2"),
+ }
+ }
+ },
+ {
+ "NUnit", new FrameworkInfo()
+ {
+ Label = "NUnit Label",
+ Description = "NUnit description",
+ Url = "https://nunit.org",
+ Dependencies = new List()
+ {
+ new("Reqnroll.NUnit", "2.4.1"),
+ new("nunit", "3.14.0"),
+ }
+ }
+ }
+ },
+ TestFrameworks = new ObservableCollection()
+ {
+ MsTest, "NUnit"
+ },
+ DotNetFrameworkLabelToTagMap = new Dictionary()
+ {
+ { Net8Label, Net8Tag}
+ }
};
#endif
From 800b2ab000411206fdcac50adaf5f9818bb453a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?=
Date: Mon, 16 Jun 2025 16:51:48 +0200
Subject: [PATCH 10/14] fix style by replacing TextBlock with Label
---
.../Dialogs/AddNewReqnrollProjectDialog.xaml | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml b/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml
index 28710173..c585f8ef 100644
--- a/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml
+++ b/Reqnroll.VisualStudio.UI/Dialogs/AddNewReqnrollProjectDialog.xaml
@@ -111,7 +111,6 @@
-