From 4d26555ec39864c49f1878bb121a70acca077340 Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Wed, 2 Aug 2023 19:00:43 +0200
Subject: [PATCH 01/16] Refactoring the project, to include UI test project

---
 .../Constants/AbsolutePaths.cs                |   7 +
 .../Constants/ElementSelectors.cs             |   7 +
 .../Constants/ExpectedContents.cs             |  12 +
 .../Constants/TestCompetitor.cs               |   8 +
 .../Constants/TestUser.cs                     |   8 +
 .../TestCaseUITestContextExtensions.cs        | 134 ++++++++
 .../Extensions/UITestContextExtensions.cs     |  36 ++
 Lombiq.JsonEditor.Test.UI/License.md          |  13 +
 .../Lombiq.JsonEditor.Tests.UI.csproj         |  33 ++
 Lombiq.JsonEditor.Test.UI/NuGetIcon.png       | Bin 0 -> 4657 bytes
 Lombiq.JsonEditor.Test.UI/Readme.md           |   7 +
 Lombiq.JsonEditor.sln                         |  25 ++
 Lombiq.JsonEditor/.editorconfig               | 310 ++++++++++++++++++
 .../Assets}/Styles/json-editor.scss           |   0
 .../Constants}/FeatureIds.cs                  |   0
 .../Constants}/ResourceNames.cs               |   0
 .../Drivers}/JsonFieldDisplayDriver.cs        |   0
 .../Fields}/JsonField.cs                      |   0
 Lombiq.JsonEditor/License.md                  |  13 +
 .../Lombiq.JsonEditor.csproj                  |   9 +-
 Lombiq.JsonEditor/Lombiq.JsonEditor.sln       |  31 ++
 Manifest.cs => Lombiq.JsonEditor/Manifest.cs  |   0
 .../Models}/JsonEditorMode.cs                 |   0
 .../Models}/JsonEditorOptions.cs              |   0
 .../Models}/JsonEditorTemplate.cs             |   0
 Lombiq.JsonEditor/NuGetIcon.png               | Bin 0 -> 4657 bytes
 .../Recipes}/JsonEditor.Sample.recipe.json    |   0
 .../ResourceManagementOptionsConfiguration.cs |   0
 .../Settings}/JsonFieldSettings.cs            |   0
 .../Settings}/JsonFieldSettingsDriver.cs      |   0
 Startup.cs => Lombiq.JsonEditor/Startup.cs    |   0
 .../TagHelpers}/JsonEditorTagHelper.cs        |   0
 .../ViewModels}/DisplayJsonFieldViewModel.cs  |   0
 .../ViewModels}/EditJsonFieldViewModel.cs     |   0
 .../Views}/JsonEditor.cshtml                  |   0
 .../Views}/JsonField.Edit.cshtml              |   0
 .../Views}/JsonField.cshtml                   |   0
 .../Views}/JsonFieldSettings.Edit.cshtml      |   0
 .../Views}/_ViewImports.cshtml                |   0
 .../package.json                              |   0
 .../pnpm-lock.yaml                            |   0
 41 files changed, 648 insertions(+), 5 deletions(-)
 create mode 100644 Lombiq.JsonEditor.Test.UI/Constants/AbsolutePaths.cs
 create mode 100644 Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs
 create mode 100644 Lombiq.JsonEditor.Test.UI/Constants/ExpectedContents.cs
 create mode 100644 Lombiq.JsonEditor.Test.UI/Constants/TestCompetitor.cs
 create mode 100644 Lombiq.JsonEditor.Test.UI/Constants/TestUser.cs
 create mode 100644 Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
 create mode 100644 Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs
 create mode 100644 Lombiq.JsonEditor.Test.UI/License.md
 create mode 100644 Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
 create mode 100644 Lombiq.JsonEditor.Test.UI/NuGetIcon.png
 create mode 100644 Lombiq.JsonEditor.Test.UI/Readme.md
 create mode 100644 Lombiq.JsonEditor.sln
 create mode 100644 Lombiq.JsonEditor/.editorconfig
 rename {Assets => Lombiq.JsonEditor/Assets}/Styles/json-editor.scss (100%)
 rename {Constants => Lombiq.JsonEditor/Constants}/FeatureIds.cs (100%)
 rename {Constants => Lombiq.JsonEditor/Constants}/ResourceNames.cs (100%)
 rename {Drivers => Lombiq.JsonEditor/Drivers}/JsonFieldDisplayDriver.cs (100%)
 rename {Fields => Lombiq.JsonEditor/Fields}/JsonField.cs (100%)
 create mode 100644 Lombiq.JsonEditor/License.md
 rename Lombiq.JsonEditor.csproj => Lombiq.JsonEditor/Lombiq.JsonEditor.csproj (76%)
 create mode 100644 Lombiq.JsonEditor/Lombiq.JsonEditor.sln
 rename Manifest.cs => Lombiq.JsonEditor/Manifest.cs (100%)
 rename {Models => Lombiq.JsonEditor/Models}/JsonEditorMode.cs (100%)
 rename {Models => Lombiq.JsonEditor/Models}/JsonEditorOptions.cs (100%)
 rename {Models => Lombiq.JsonEditor/Models}/JsonEditorTemplate.cs (100%)
 create mode 100644 Lombiq.JsonEditor/NuGetIcon.png
 rename {Recipes => Lombiq.JsonEditor/Recipes}/JsonEditor.Sample.recipe.json (100%)
 rename ResourceManagementOptionsConfiguration.cs => Lombiq.JsonEditor/ResourceManagementOptionsConfiguration.cs (100%)
 rename {Settings => Lombiq.JsonEditor/Settings}/JsonFieldSettings.cs (100%)
 rename {Settings => Lombiq.JsonEditor/Settings}/JsonFieldSettingsDriver.cs (100%)
 rename Startup.cs => Lombiq.JsonEditor/Startup.cs (100%)
 rename {TagHelpers => Lombiq.JsonEditor/TagHelpers}/JsonEditorTagHelper.cs (100%)
 rename {ViewModels => Lombiq.JsonEditor/ViewModels}/DisplayJsonFieldViewModel.cs (100%)
 rename {ViewModels => Lombiq.JsonEditor/ViewModels}/EditJsonFieldViewModel.cs (100%)
 rename {Views => Lombiq.JsonEditor/Views}/JsonEditor.cshtml (100%)
 rename {Views => Lombiq.JsonEditor/Views}/JsonField.Edit.cshtml (100%)
 rename {Views => Lombiq.JsonEditor/Views}/JsonField.cshtml (100%)
 rename {Views => Lombiq.JsonEditor/Views}/JsonFieldSettings.Edit.cshtml (100%)
 rename {Views => Lombiq.JsonEditor/Views}/_ViewImports.cshtml (100%)
 rename package.json => Lombiq.JsonEditor/package.json (100%)
 rename pnpm-lock.yaml => Lombiq.JsonEditor/pnpm-lock.yaml (100%)

diff --git a/Lombiq.JsonEditor.Test.UI/Constants/AbsolutePaths.cs b/Lombiq.JsonEditor.Test.UI/Constants/AbsolutePaths.cs
new file mode 100644
index 0000000..cd94c9a
--- /dev/null
+++ b/Lombiq.JsonEditor.Test.UI/Constants/AbsolutePaths.cs
@@ -0,0 +1,7 @@
+namespace Lombiq.Privacy.Tests.UI.Constants;
+
+public static class AbsolutePaths
+{
+    public const string CompetitorRegistration = "/competitor-registration";
+    public const string ErrorPage = "/error";
+}
diff --git a/Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs b/Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs
new file mode 100644
index 0000000..f5b28de
--- /dev/null
+++ b/Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs
@@ -0,0 +1,7 @@
+namespace Lombiq.Privacy.Tests.UI.Constants;
+
+public static class ElementSelectors
+{
+    public const string PrivacyConsentAcceptButtonId = "privacy-consent-accept-button";
+    public const string PrivacyConsentCheckboxLabelCss = "div.form-check > label";
+}
diff --git a/Lombiq.JsonEditor.Test.UI/Constants/ExpectedContents.cs b/Lombiq.JsonEditor.Test.UI/Constants/ExpectedContents.cs
new file mode 100644
index 0000000..d8cf305
--- /dev/null
+++ b/Lombiq.JsonEditor.Test.UI/Constants/ExpectedContents.cs
@@ -0,0 +1,12 @@
+namespace Lombiq.Privacy.Tests.UI.Constants;
+
+public static class ExpectedContents
+{
+    public const string ConsentBannerContent = "Our site uses browser cookies to personalize your experience. "
+        + "Regarding this issue, please find our information on data control and processing here and our information"
+        + " on server logging and usage of so-called 'cookies' here. "
+        + "By clicking here You confirm your approval of the data processing activities mentioned in these documents. "
+        + "Please note that You may only use this website efficiently if you agree to these conditions.";
+    public const string RegistrationConsentCheckboxContent = "I've read and agree to the site's privacy policy.";
+    public const string FormConsentCheckboxContent = "I've read and agree to the site's privacy policy.";
+}
diff --git a/Lombiq.JsonEditor.Test.UI/Constants/TestCompetitor.cs b/Lombiq.JsonEditor.Test.UI/Constants/TestCompetitor.cs
new file mode 100644
index 0000000..3e0c19a
--- /dev/null
+++ b/Lombiq.JsonEditor.Test.UI/Constants/TestCompetitor.cs
@@ -0,0 +1,8 @@
+namespace Lombiq.Privacy.Tests.UI.Constants;
+
+public static class TestCompetitor
+{
+    public const string FullName = "Jane Doe";
+    public const string Age = "23";
+    public const string Email = "jane.doe@lombiq.com";
+}
diff --git a/Lombiq.JsonEditor.Test.UI/Constants/TestUser.cs b/Lombiq.JsonEditor.Test.UI/Constants/TestUser.cs
new file mode 100644
index 0000000..611600a
--- /dev/null
+++ b/Lombiq.JsonEditor.Test.UI/Constants/TestUser.cs
@@ -0,0 +1,8 @@
+namespace Lombiq.Privacy.Tests.UI.Constants;
+
+public static class TestUser
+{
+    public const string Name = "johndoe"; // #spell-check-ignore-line
+    public const string Password = "Password1!";
+    public const string Email = "jon.doe@lombiq.com"; // #spell-check-ignore-line
+}
diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
new file mode 100644
index 0000000..beee7e7
--- /dev/null
+++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
@@ -0,0 +1,134 @@
+using Lombiq.Privacy.Tests.UI.Constants;
+using Lombiq.Tests.UI.Extensions;
+using Lombiq.Tests.UI.Services;
+using OpenQA.Selenium;
+using OrchardCore.Users.Models;
+using Shouldly;
+using System.Threading.Tasks;
+
+namespace Lombiq.Privacy.Tests.UI.Extensions;
+
+public static class TestCaseUITestContextExtensions
+{
+    public static async Task TestPrivacySampleBehaviorAsync(this UITestContext context)
+    {
+        await context.ExecutePrivacySampleRecipeDirectlyAsync();
+
+        await context.TestFormConsentCheckboxContentAsync();
+
+        await context.GoToRelativeUrlAsync(AbsolutePaths.CompetitorRegistration);
+
+        // Here we fill up the competitor form.
+        await context.FillInWithRetriesAsync(By.Id("full-name"), TestCompetitor.FullName);
+        await context.FillInWithRetriesAsync(By.Id("age"), TestCompetitor.Age);
+        await context.FillInWithRetriesAsync(By.Id("e-mail"), TestCompetitor.Email);
+        await context.ClickReliablyOnSubmitAsync();
+
+        // Consent checkbox left unchecked, so after clicking on submit navigation should not happens.
+        context.GetCurrentUri().AbsolutePath.ShouldBe(AbsolutePaths.CompetitorRegistration);
+
+        // Now we set consent checkbox to checked.
+        await context.SetCheckboxValueAsync(By.Id("PrivacyConsentCheckboxPart_ConsentCheckbox"), isChecked: true);
+        await context.ClickReliablyOnSubmitAsync();
+
+        // After submit, it navigates to the new content items view.
+        context.GetCurrentUri().AbsolutePath
+            .ShouldNotBeOneOf(AbsolutePaths.ErrorPage, AbsolutePaths.CompetitorRegistration);
+    }
+
+    public static async Task TestFormConsentCheckboxContentAsync(this UITestContext context)
+    {
+        await context.GoToRelativeUrlAsync(AbsolutePaths.CompetitorRegistration);
+
+        context.VerifyElementTexts(
+            By.CssSelector(ElementSelectors.PrivacyConsentCheckboxLabelCss),
+            ExpectedContents.FormConsentCheckboxContent);
+    }
+
+    public static async Task TestConsentBannerWithThemeAsync(this UITestContext context, string theme)
+    {
+        await context.SelectThemeAsync(theme);
+        await context.SignInDirectlyAsync();
+        await context.TestConsentBannerAsync();
+    }
+
+    public static async Task TestConsentBannerAsync(this UITestContext context)
+    {
+        await context.EnablePrivacyConsentBannerFeatureAsync();
+        await context.GoToHomePageAsync(onlyIfNotAlreadyThere: false);
+        await context.TestConsentBannerContentAsync();
+        await context.TestConsentBannerAcceptButtonAsync();
+    }
+
+    public static async Task TestConsentBannerContentAsync(this UITestContext context)
+    {
+        await context.GoToHomePageAsync();
+
+        context.VerifyElementTexts(
+            By.CssSelector("#privacy-consent-banner > .modal-content > .modal-body"),
+            ExpectedContents.ConsentBannerContent);
+    }
+
+    public static async Task TestConsentBannerAcceptButtonAsync(this UITestContext context)
+    {
+        await context.GoToHomePageAsync();
+
+        var privacyConsentAcceptButtonBy = By.Id(ElementSelectors.PrivacyConsentAcceptButtonId);
+
+        void AssertPrivacyConsentAcceptButtonMissing()
+        {
+            try
+            {
+                context.Missing(privacyConsentAcceptButtonBy);
+            }
+            catch (StaleElementReferenceException)
+            {
+                // If the banner disappears when we're in the middle of Missing() then unlucky timing can cause it to
+                // get properties of the now vanished element, causing a StaleElementReferenceException. This is not a
+                // problem, so we may continue.
+            }
+        }
+
+        await context.ClickReliablyOnAsync(privacyConsentAcceptButtonBy);
+        AssertPrivacyConsentAcceptButtonMissing();
+
+        // Verify persistence.
+        context.Refresh();
+        AssertPrivacyConsentAcceptButtonMissing();
+    }
+
+    public static async Task TestRegistrationConsentCheckboxAsync(this UITestContext context)
+    {
+        await context.EnablePrivacyConsentBannerFeatureAsync();
+        await context.EnablePrivacyRegistrationConsentFeatureAsync();
+        await context.SetUserRegistrationTypeAsync(UserRegistrationType.AllowRegistration);
+
+        await context.TestRegistrationConsentCheckboxContentAsync();
+
+        // Go to registration and create a new user.
+        await context.GoToRegistrationPageAsync();
+        await context.FillInWithRetriesAsync(By.Id("UserName"), TestUser.Name);
+        await context.FillInWithRetriesAsync(By.Id("Email"), TestUser.Email);
+        await context.FillInWithRetriesAsync(By.Id("Password"), TestUser.Password);
+        await context.FillInWithRetriesAsync(By.Id("ConfirmPassword"), TestUser.Password);
+        await context.SetCheckboxValueAsync(By.Id("RegistrationCheckbox"), isChecked: true);
+        await context.ClickReliablyOnSubmitAsync();
+
+        // Login with the created user.
+        await context.SignInDirectlyAsync(TestUser.Name);
+        (await context.GetCurrentUserNameAsync()).ShouldBe(TestUser.Name);
+
+        // Check that, the consent banner doesn't come up after login.
+        await context.GoToHomePageAsync();
+        context.Missing(By.Id(ElementSelectors.PrivacyConsentAcceptButtonId));
+    }
+
+    public static async Task TestRegistrationConsentCheckboxContentAsync(this UITestContext context)
+    {
+        await context.GoToRegistrationPageAsync();
+
+        context.VerifyElementTexts(
+            By.CssSelector(ElementSelectors.PrivacyConsentCheckboxLabelCss),
+            ExpectedContents.RegistrationConsentCheckboxContent);
+    }
+}
diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs
new file mode 100644
index 0000000..bd6636f
--- /dev/null
+++ b/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs
@@ -0,0 +1,36 @@
+using Lombiq.Privacy.Constants;
+using Lombiq.Privacy.Tests.UI.Constants;
+using Lombiq.Tests.UI.Extensions;
+using Lombiq.Tests.UI.Services;
+using OpenQA.Selenium;
+using System.Threading.Tasks;
+
+namespace Lombiq.Privacy.Tests.UI.Extensions;
+
+public static class UITestContextExtensions
+{
+    public static Task ExecutePrivacySampleRecipeDirectlyAsync(this UITestContext context) =>
+        context.ExecuteRecipeDirectlyAsync("Lombiq.Privacy.Samples");
+
+    public static Task EnablePrivacyConsentBannerFeatureAsync(this UITestContext context) =>
+        context.EnableFeatureDirectlyAsync(FeatureNames.ConsentBanner);
+
+    public static Task EnablePrivacyRegistrationConsentFeatureAsync(this UITestContext context) =>
+        context.EnableFeatureDirectlyAsync(FeatureNames.RegistrationConsent);
+
+    /// <remarks>
+    /// <para>
+    /// This is necessary for Application Insights tracking, even in offline mode.
+    /// </para>
+    /// </remarks>
+    public static Task AcceptPrivacyConsentAsync(this UITestContext context) =>
+        context.ClickReliablyOnAsync(By.Id(ElementSelectors.PrivacyConsentAcceptButtonId));
+
+    public static async Task EnablePrivacyConsentBannerFeatureAndAcceptPrivacyConsentAsync(this UITestContext context)
+    {
+        await context.EnablePrivacyConsentBannerFeatureAsync();
+        await context.GoToHomePageAsync(onlyIfNotAlreadyThere: false);
+        await context.AcceptPrivacyConsentAsync();
+        context.Refresh();
+    }
+}
diff --git a/Lombiq.JsonEditor.Test.UI/License.md b/Lombiq.JsonEditor.Test.UI/License.md
new file mode 100644
index 0000000..4add106
--- /dev/null
+++ b/Lombiq.JsonEditor.Test.UI/License.md
@@ -0,0 +1,13 @@
+Copyright © 2023, [Lombiq Technologies Ltd.](https://lombiq.com)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj b/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
new file mode 100644
index 0000000..afdd441
--- /dev/null
+++ b/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
@@ -0,0 +1,33 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <Title>Lombiq JSON Editor for Orchard Core - UI Test Extensions</Title>
+    <Authors>Lombiq Technologies</Authors>
+    <Copyright>Copyright © 2023, Lombiq Technologies Ltd.</Copyright>
+    <Description>Lombiq JSON Editor for Orchard Core - UI Test Extensions: Extensions to aid in UI testing Lombiq JSON Editor for Orchard Core.</Description>
+    <PackageIcon>NuGetIcon.png</PackageIcon>
+    <PackageTags>OrchardCore;Lombiq;AspNetCore;JSONEditor</PackageTags>
+    <RepositoryUrl>https://github.com/Lombiq/Orchard-JSON-Editor</RepositoryUrl>
+    <PackageProjectUrl>https://github.com/Lombiq/Orchard-JSON-Editor/tree/dev/Lombiq.JSONEditor.Tests.UI</PackageProjectUrl>
+    <PackageLicenseFile>License.md</PackageLicenseFile>
+  </PropertyGroup>
+
+  <ItemGroup Condition="'$(NuGetBuild)' != 'true'">
+    <ProjectReference Include="..\..\..\..\test\Lombiq.UITestingToolbox\Lombiq.Tests.UI\Lombiq.Tests.UI.csproj" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(NuGetBuild)' == 'true'">
+    <PackageReference Include="Lombiq.Tests.UI" Version="7.0.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Include="License.md" Pack="true" PackagePath="" />
+    <None Include="NuGetIcon.png" Pack="true" PackagePath="" />
+    <None Include="Readme.md" />
+  </ItemGroup>
+
+</Project>
diff --git a/Lombiq.JsonEditor.Test.UI/NuGetIcon.png b/Lombiq.JsonEditor.Test.UI/NuGetIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..162a00508d8041833604d427216238c1b23b2d47
GIT binary patch
literal 4657
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

literal 0
HcmV?d00001

diff --git a/Lombiq.JsonEditor.Test.UI/Readme.md b/Lombiq.JsonEditor.Test.UI/Readme.md
new file mode 100644
index 0000000..cecef5c
--- /dev/null
+++ b/Lombiq.JsonEditor.Test.UI/Readme.md
@@ -0,0 +1,7 @@
+# Lombiq JSON Editor for Orchard Core - UI Test Extensions
+
+## About
+
+Extension methods that test various features in Lombiq JSON Editor for Orchard Core, with the help of [Lombiq UI Testing Toolbox for Orchard Core](https://github.com/Lombiq/UI-Testing-Toolbox).
+
+Call these from a UI test project to verify the module's basic features; as seen in [Open-Source Orchard Core Extensions](https://github.com/Lombiq/Open-Source-Orchard-Core-Extensions).
diff --git a/Lombiq.JsonEditor.sln b/Lombiq.JsonEditor.sln
new file mode 100644
index 0000000..81e2879
--- /dev/null
+++ b/Lombiq.JsonEditor.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.6.33815.320
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lombiq.JsonEditor", "Lombiq.JsonEditor\Lombiq.JsonEditor.csproj", "{3EA5BC81-97A5-4513-9CAC-4DB01B439682}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {67F3FCAA-D525-42C7-9743-B8D896F485BA}
+	EndGlobalSection
+EndGlobal
diff --git a/Lombiq.JsonEditor/.editorconfig b/Lombiq.JsonEditor/.editorconfig
new file mode 100644
index 0000000..1791262
--- /dev/null
+++ b/Lombiq.JsonEditor/.editorconfig
@@ -0,0 +1,310 @@
+# WARNING: Only edit this file in the Lombiq .NET Analyzers repository's "Lombiq.Analyzers" folder. A copy of this file
+# anywhere else will be overwritten.
+
+# Common .NET conventions, code formatting and naming convention rules. Check out possible configs here:
+# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
+# If you found some issue then try to fix it, which you can also do from VS Quick Actions:
+# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#automatically-configure-code-styles
+# Add new file types at the bottom. For new rules for C# files follow the structure that VS generates.
+
+# All files
+[*]
+
+# Basics
+charset = utf-8
+guidelines = 120 1px solid a0ffc000, 150 1px solid 80ff0000
+
+# Indentation and spacing
+indent_size = 4
+indent_style = space
+trim_trailing_whitespace = true
+
+
+# New line preferences
+end_of_line = crlf
+insert_final_newline = true
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_null_propagation = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+
+# C# files
+[*.cs]
+
+#### .NET Coding Conventions ####
+
+# Organize usings
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = false
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:warning
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_property = false:warning
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:warning
+dotnet_style_predefined_type_for_member_access = true:warning
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = always:warning
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_collection_initializer = true:warning
+dotnet_style_explicit_tuple_names = true:warning
+dotnet_style_null_propagation = true:warning
+dotnet_style_object_initializer = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_compound_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_return = false
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
+dotnet_style_prefer_inferred_tuple_names = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_simplified_interpolation = true:warning
+
+# Field preferences
+dotnet_style_readonly_field = true:warning
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = non_pubic:suggestion
+
+#### C# Style Rules ####
+
+#IDE* rules are managed here.
+
+# Note that currently both IDE* rules and csharp_style_* rules are necessary, because only IDE rules will be enforced
+# during build, see: https://github.com/dotnet/roslyn/issues/44201.
+
+# Default severity for analyzer diagnostics for all categories (escalated to build warnings). See
+# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/categories for the list of categories.
+dotnet_analyzer_diagnostic.category-Design.severity = warning
+dotnet_analyzer_diagnostic.category-Documentation.severity = warning
+dotnet_analyzer_diagnostic.category-Globalization.severity = warning
+dotnet_analyzer_diagnostic.category-Interoperability.severity = warning
+dotnet_analyzer_diagnostic.category-Maintainability.severity = warning
+dotnet_analyzer_diagnostic.category-Naming.severity = warning
+dotnet_analyzer_diagnostic.category-Performance.severity = warning
+dotnet_analyzer_diagnostic.category-SingleFile.severity = warning
+dotnet_analyzer_diagnostic.category-Reliability.severity = warning
+dotnet_analyzer_diagnostic.category-Security.severity = warning
+dotnet_analyzer_diagnostic.category-Style.severity = warning
+dotnet_analyzer_diagnostic.category-Usage.severity = warning
+dotnet_analyzer_diagnostic.category-CodeQuality.severity = warning
+
+# IDE0011: Add braces to 'if' statement.
+# The "when-multiline:warning" config is not actually for cases when the if body is in another line so we have to turn
+# this off completely, see: https://github.com/dotnet/roslyn/issues/40912.
+dotnet_diagnostic.IDE0011.severity = none
+# IDE0050: Convert to tuple
+# Quite dangerous as we most frequently use anonymous types to interface with other APIs (like generating routes) and
+# those can fail on this silently in runtime while building correctly.
+dotnet_diagnostic.IDE0050.severity = none
+# IDE0052: Private member can be removed as the value assigned to it is never used.
+# We use S4487 for that.
+dotnet_diagnostic.IDE0052.severity = none
+# IDE0072: Populate switch
+# Also signals on switches with discards, by design: https://github.com/dotnet/roslyn/issues/48876.
+dotnet_diagnostic.IDE0072.severity = none
+# "Namespace 'Foo' does not match folder structure, expected 'Foo'"
+dotnet_diagnostic.IDE0130.severity = none
+
+# While these are in the Style category, they need to be explicitly set for some reason.
+# IDE0079 Remove unnecessary suppression
+dotnet_diagnostic.IDE0079.severity = warning
+dotnet_diagnostic.IDE0022.severity = warning
+
+# 'var' preferences
+dotnet_diagnostic.IDE0007.severity = warning
+dotnet_diagnostic.IDE0008.severity = none
+
+# We don't want to decorate every public member with XML comments, only public APIs - so turn CS1591 off.
+# CS1591 Missing XML Comment for publicly visible type or member
+dotnet_diagnostic.CS1591.severity = none
+
+#### C# Coding Conventions ####
+
+# 'var' preferences
+# These won't take effect during build due to this bug:
+# https://github.com/dotnet/roslyn/issues/44250
+csharp_style_var_elsewhere = true:warning
+# If this is not turned off then there will be messages for e.g. integers too, like in for loops.
+csharp_style_var_for_built_in_types = false:none
+csharp_style_var_when_type_is_apparent = true:warning
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:warning
+csharp_style_expression_bodied_constructors = true:warning
+csharp_style_expression_bodied_indexers = true:warning
+csharp_style_expression_bodied_lambdas = true:warning
+csharp_style_expression_bodied_local_functions = true:warning
+csharp_style_expression_bodied_methods = true:warning
+csharp_style_expression_bodied_operators = true:warning
+csharp_style_expression_bodied_properties = true:warning
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+csharp_style_prefer_switch_expression = true:warning
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:warning
+
+# Modifier preferences
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
+
+# Code-block preferences
+csharp_prefer_simple_using_statement = true:warning
+# IDE0160: Convert to block-scoped namespace
+csharp_style_namespace_declarations = file_scoped:warning
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:warning
+csharp_style_deconstructed_variable_declaration = false:silent
+csharp_style_inlined_variable_declaration = true:warning
+csharp_style_pattern_local_over_anonymous_function = true:warning
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
+csharp_style_throw_expression = true:warning
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:none
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:silent
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = no_change
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = *
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = *
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = *
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+# Disabling "catch a more specific exception type" suggestion which is overwhelmingly a false positive.
+dotnet_diagnostic.CA1031.severity = silent
+# Disabling "do not nest type" suggestion.
+dotnet_diagnostic.CA1034.severity = silent
+# Disabling "CA1810: Initialize reference type static fields inline" as RSPEC-3963 is used instead.
+dotnet_diagnostic.CA1810.severity = none
+
+# Don't apply "CA1822: Mark members as static" to the public API
+dotnet_code_quality.ca1822.api_surface = private, internal
+csharp_prefer_braces = true:silent
+
+# Various config files
+[*.{config,csproj,json,props,targets}]
+
+indent_size = 2
+
+# Markdown files
+[*.md]
+
+trim_trailing_whitespace = false
+
+# JavaScript files
+[*.js]
+
+# Placeholder, no unique rules for JS files at the moment.
+
+
+# SCSS files
+[*.scss]
+
+# Placeholder, no unique rules for SCSS files at the moment.
+
+# PowerShell files
+[*.ps1]
+
+# Placeholder, no unique rules for PS files at the moment.
diff --git a/Assets/Styles/json-editor.scss b/Lombiq.JsonEditor/Assets/Styles/json-editor.scss
similarity index 100%
rename from Assets/Styles/json-editor.scss
rename to Lombiq.JsonEditor/Assets/Styles/json-editor.scss
diff --git a/Constants/FeatureIds.cs b/Lombiq.JsonEditor/Constants/FeatureIds.cs
similarity index 100%
rename from Constants/FeatureIds.cs
rename to Lombiq.JsonEditor/Constants/FeatureIds.cs
diff --git a/Constants/ResourceNames.cs b/Lombiq.JsonEditor/Constants/ResourceNames.cs
similarity index 100%
rename from Constants/ResourceNames.cs
rename to Lombiq.JsonEditor/Constants/ResourceNames.cs
diff --git a/Drivers/JsonFieldDisplayDriver.cs b/Lombiq.JsonEditor/Drivers/JsonFieldDisplayDriver.cs
similarity index 100%
rename from Drivers/JsonFieldDisplayDriver.cs
rename to Lombiq.JsonEditor/Drivers/JsonFieldDisplayDriver.cs
diff --git a/Fields/JsonField.cs b/Lombiq.JsonEditor/Fields/JsonField.cs
similarity index 100%
rename from Fields/JsonField.cs
rename to Lombiq.JsonEditor/Fields/JsonField.cs
diff --git a/Lombiq.JsonEditor/License.md b/Lombiq.JsonEditor/License.md
new file mode 100644
index 0000000..d57e130
--- /dev/null
+++ b/Lombiq.JsonEditor/License.md
@@ -0,0 +1,13 @@
+Copyright © 2021, [Lombiq Technologies Ltd.](https://lombiq.com)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Lombiq.JsonEditor.csproj b/Lombiq.JsonEditor/Lombiq.JsonEditor.csproj
similarity index 76%
rename from Lombiq.JsonEditor.csproj
rename to Lombiq.JsonEditor/Lombiq.JsonEditor.csproj
index cf08efd..05d644c 100644
--- a/Lombiq.JsonEditor.csproj
+++ b/Lombiq.JsonEditor/Lombiq.JsonEditor.csproj
@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk.Razor">
 
-  <Import Condition="'$(NuGetBuild)' != 'true'" Project="..\..\Utilities\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions.props" />
+  <Import Condition="'$(NuGetBuild)' != 'true'" Project="..\..\..\Utilities\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions.props" />
 
   <PropertyGroup>
     <TargetFramework>net6.0</TargetFramework>
@@ -22,7 +22,6 @@
 
   <ItemGroup>
     <None Include="License.md" Pack="true" PackagePath="" />
-    <None Include="Readme.md" />
     <None Include="NuGetIcon.png" Pack="true" PackagePath="" />
   </ItemGroup>
 
@@ -44,8 +43,8 @@
   </ItemGroup>
 
   <ItemGroup Condition="'$(NuGetBuild)' != 'true'">
-    <ProjectReference Include="..\..\Libraries\Lombiq.HelpfulLibraries\Lombiq.HelpfulLibraries.OrchardCore\Lombiq.HelpfulLibraries.OrchardCore.csproj" />
-    <ProjectReference Include="..\..\Utilities\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions.csproj" />
+    <ProjectReference Include="..\..\..\Libraries\Lombiq.HelpfulLibraries\Lombiq.HelpfulLibraries.OrchardCore\Lombiq.HelpfulLibraries.OrchardCore.csproj" />
+    <ProjectReference Include="..\..\..\Utilities\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions.csproj" />
   </ItemGroup>
 
   <ItemGroup Condition="'$(NuGetBuild)' == 'true'">
@@ -53,6 +52,6 @@
     <PackageReference Include="Lombiq.NodeJs.Extensions" Version="1.2.4" />
   </ItemGroup>
 
-  <Import Condition="'$(NuGetBuild)' != 'true'" Project="..\..\Utilities\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions.targets" />
+  <Import Condition="'$(NuGetBuild)' != 'true'" Project="..\..\..\Utilities\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions\Lombiq.NodeJs.Extensions.targets" />
 
 </Project>
diff --git a/Lombiq.JsonEditor/Lombiq.JsonEditor.sln b/Lombiq.JsonEditor/Lombiq.JsonEditor.sln
new file mode 100644
index 0000000..98a8431
--- /dev/null
+++ b/Lombiq.JsonEditor/Lombiq.JsonEditor.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.6.33815.320
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.JsonEditor", "Lombiq.JsonEditor.csproj", "{CBBDBDAF-53C0-48DC-9020-2F73C5C6F687}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.JsonEditor.Tests.UI", "..\Lombiq.JsonEditor.Test.UI\Lombiq.JsonEditor.Tests.UI.csproj", "{EE600BED-156B-4D25-8861-056D56AA1FD0}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{CBBDBDAF-53C0-48DC-9020-2F73C5C6F687}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{CBBDBDAF-53C0-48DC-9020-2F73C5C6F687}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{CBBDBDAF-53C0-48DC-9020-2F73C5C6F687}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{CBBDBDAF-53C0-48DC-9020-2F73C5C6F687}.Release|Any CPU.Build.0 = Release|Any CPU
+		{EE600BED-156B-4D25-8861-056D56AA1FD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{EE600BED-156B-4D25-8861-056D56AA1FD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EE600BED-156B-4D25-8861-056D56AA1FD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{EE600BED-156B-4D25-8861-056D56AA1FD0}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {67F3FCAA-D525-42C7-9743-B8D896F485BA}
+	EndGlobalSection
+EndGlobal
diff --git a/Manifest.cs b/Lombiq.JsonEditor/Manifest.cs
similarity index 100%
rename from Manifest.cs
rename to Lombiq.JsonEditor/Manifest.cs
diff --git a/Models/JsonEditorMode.cs b/Lombiq.JsonEditor/Models/JsonEditorMode.cs
similarity index 100%
rename from Models/JsonEditorMode.cs
rename to Lombiq.JsonEditor/Models/JsonEditorMode.cs
diff --git a/Models/JsonEditorOptions.cs b/Lombiq.JsonEditor/Models/JsonEditorOptions.cs
similarity index 100%
rename from Models/JsonEditorOptions.cs
rename to Lombiq.JsonEditor/Models/JsonEditorOptions.cs
diff --git a/Models/JsonEditorTemplate.cs b/Lombiq.JsonEditor/Models/JsonEditorTemplate.cs
similarity index 100%
rename from Models/JsonEditorTemplate.cs
rename to Lombiq.JsonEditor/Models/JsonEditorTemplate.cs
diff --git a/Lombiq.JsonEditor/NuGetIcon.png b/Lombiq.JsonEditor/NuGetIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..162a00508d8041833604d427216238c1b23b2d47
GIT binary patch
literal 4657
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

literal 0
HcmV?d00001

diff --git a/Recipes/JsonEditor.Sample.recipe.json b/Lombiq.JsonEditor/Recipes/JsonEditor.Sample.recipe.json
similarity index 100%
rename from Recipes/JsonEditor.Sample.recipe.json
rename to Lombiq.JsonEditor/Recipes/JsonEditor.Sample.recipe.json
diff --git a/ResourceManagementOptionsConfiguration.cs b/Lombiq.JsonEditor/ResourceManagementOptionsConfiguration.cs
similarity index 100%
rename from ResourceManagementOptionsConfiguration.cs
rename to Lombiq.JsonEditor/ResourceManagementOptionsConfiguration.cs
diff --git a/Settings/JsonFieldSettings.cs b/Lombiq.JsonEditor/Settings/JsonFieldSettings.cs
similarity index 100%
rename from Settings/JsonFieldSettings.cs
rename to Lombiq.JsonEditor/Settings/JsonFieldSettings.cs
diff --git a/Settings/JsonFieldSettingsDriver.cs b/Lombiq.JsonEditor/Settings/JsonFieldSettingsDriver.cs
similarity index 100%
rename from Settings/JsonFieldSettingsDriver.cs
rename to Lombiq.JsonEditor/Settings/JsonFieldSettingsDriver.cs
diff --git a/Startup.cs b/Lombiq.JsonEditor/Startup.cs
similarity index 100%
rename from Startup.cs
rename to Lombiq.JsonEditor/Startup.cs
diff --git a/TagHelpers/JsonEditorTagHelper.cs b/Lombiq.JsonEditor/TagHelpers/JsonEditorTagHelper.cs
similarity index 100%
rename from TagHelpers/JsonEditorTagHelper.cs
rename to Lombiq.JsonEditor/TagHelpers/JsonEditorTagHelper.cs
diff --git a/ViewModels/DisplayJsonFieldViewModel.cs b/Lombiq.JsonEditor/ViewModels/DisplayJsonFieldViewModel.cs
similarity index 100%
rename from ViewModels/DisplayJsonFieldViewModel.cs
rename to Lombiq.JsonEditor/ViewModels/DisplayJsonFieldViewModel.cs
diff --git a/ViewModels/EditJsonFieldViewModel.cs b/Lombiq.JsonEditor/ViewModels/EditJsonFieldViewModel.cs
similarity index 100%
rename from ViewModels/EditJsonFieldViewModel.cs
rename to Lombiq.JsonEditor/ViewModels/EditJsonFieldViewModel.cs
diff --git a/Views/JsonEditor.cshtml b/Lombiq.JsonEditor/Views/JsonEditor.cshtml
similarity index 100%
rename from Views/JsonEditor.cshtml
rename to Lombiq.JsonEditor/Views/JsonEditor.cshtml
diff --git a/Views/JsonField.Edit.cshtml b/Lombiq.JsonEditor/Views/JsonField.Edit.cshtml
similarity index 100%
rename from Views/JsonField.Edit.cshtml
rename to Lombiq.JsonEditor/Views/JsonField.Edit.cshtml
diff --git a/Views/JsonField.cshtml b/Lombiq.JsonEditor/Views/JsonField.cshtml
similarity index 100%
rename from Views/JsonField.cshtml
rename to Lombiq.JsonEditor/Views/JsonField.cshtml
diff --git a/Views/JsonFieldSettings.Edit.cshtml b/Lombiq.JsonEditor/Views/JsonFieldSettings.Edit.cshtml
similarity index 100%
rename from Views/JsonFieldSettings.Edit.cshtml
rename to Lombiq.JsonEditor/Views/JsonFieldSettings.Edit.cshtml
diff --git a/Views/_ViewImports.cshtml b/Lombiq.JsonEditor/Views/_ViewImports.cshtml
similarity index 100%
rename from Views/_ViewImports.cshtml
rename to Lombiq.JsonEditor/Views/_ViewImports.cshtml
diff --git a/package.json b/Lombiq.JsonEditor/package.json
similarity index 100%
rename from package.json
rename to Lombiq.JsonEditor/package.json
diff --git a/pnpm-lock.yaml b/Lombiq.JsonEditor/pnpm-lock.yaml
similarity index 100%
rename from pnpm-lock.yaml
rename to Lombiq.JsonEditor/pnpm-lock.yaml

From 8bb88a5bed0864376bd37a4581b2c8f16e469b67 Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Wed, 2 Aug 2023 20:57:43 +0200
Subject: [PATCH 02/16] Correcting name spaces

---
 .../Constants/AbsolutePaths.cs                |   7 -
 .../Constants/ElementSelectors.cs             |   2 +-
 .../Constants/ExpectedContents.cs             |  12 --
 .../Constants/TestCompetitor.cs               |   8 --
 .../Constants/TestUser.cs                     |   8 --
 .../TestCaseUITestContextExtensions.cs        | 128 ++----------------
 .../Extensions/UITestContextExtensions.cs     |  33 +----
 .../Lombiq.JsonEditor.Tests.UI.csproj         |   4 +
 8 files changed, 23 insertions(+), 179 deletions(-)
 delete mode 100644 Lombiq.JsonEditor.Test.UI/Constants/AbsolutePaths.cs
 delete mode 100644 Lombiq.JsonEditor.Test.UI/Constants/ExpectedContents.cs
 delete mode 100644 Lombiq.JsonEditor.Test.UI/Constants/TestCompetitor.cs
 delete mode 100644 Lombiq.JsonEditor.Test.UI/Constants/TestUser.cs

diff --git a/Lombiq.JsonEditor.Test.UI/Constants/AbsolutePaths.cs b/Lombiq.JsonEditor.Test.UI/Constants/AbsolutePaths.cs
deleted file mode 100644
index cd94c9a..0000000
--- a/Lombiq.JsonEditor.Test.UI/Constants/AbsolutePaths.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Lombiq.Privacy.Tests.UI.Constants;
-
-public static class AbsolutePaths
-{
-    public const string CompetitorRegistration = "/competitor-registration";
-    public const string ErrorPage = "/error";
-}
diff --git a/Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs b/Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs
index f5b28de..80a7565 100644
--- a/Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs
+++ b/Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs
@@ -1,4 +1,4 @@
-namespace Lombiq.Privacy.Tests.UI.Constants;
+namespace Lombiq.JsonEditor.Tests.UI.Constants;
 
 public static class ElementSelectors
 {
diff --git a/Lombiq.JsonEditor.Test.UI/Constants/ExpectedContents.cs b/Lombiq.JsonEditor.Test.UI/Constants/ExpectedContents.cs
deleted file mode 100644
index d8cf305..0000000
--- a/Lombiq.JsonEditor.Test.UI/Constants/ExpectedContents.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Lombiq.Privacy.Tests.UI.Constants;
-
-public static class ExpectedContents
-{
-    public const string ConsentBannerContent = "Our site uses browser cookies to personalize your experience. "
-        + "Regarding this issue, please find our information on data control and processing here and our information"
-        + " on server logging and usage of so-called 'cookies' here. "
-        + "By clicking here You confirm your approval of the data processing activities mentioned in these documents. "
-        + "Please note that You may only use this website efficiently if you agree to these conditions.";
-    public const string RegistrationConsentCheckboxContent = "I've read and agree to the site's privacy policy.";
-    public const string FormConsentCheckboxContent = "I've read and agree to the site's privacy policy.";
-}
diff --git a/Lombiq.JsonEditor.Test.UI/Constants/TestCompetitor.cs b/Lombiq.JsonEditor.Test.UI/Constants/TestCompetitor.cs
deleted file mode 100644
index 3e0c19a..0000000
--- a/Lombiq.JsonEditor.Test.UI/Constants/TestCompetitor.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Lombiq.Privacy.Tests.UI.Constants;
-
-public static class TestCompetitor
-{
-    public const string FullName = "Jane Doe";
-    public const string Age = "23";
-    public const string Email = "jane.doe@lombiq.com";
-}
diff --git a/Lombiq.JsonEditor.Test.UI/Constants/TestUser.cs b/Lombiq.JsonEditor.Test.UI/Constants/TestUser.cs
deleted file mode 100644
index 611600a..0000000
--- a/Lombiq.JsonEditor.Test.UI/Constants/TestUser.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Lombiq.Privacy.Tests.UI.Constants;
-
-public static class TestUser
-{
-    public const string Name = "johndoe"; // #spell-check-ignore-line
-    public const string Password = "Password1!";
-    public const string Email = "jon.doe@lombiq.com"; // #spell-check-ignore-line
-}
diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
index beee7e7..1f84c42 100644
--- a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
+++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
@@ -1,134 +1,30 @@
-using Lombiq.Privacy.Tests.UI.Constants;
 using Lombiq.Tests.UI.Extensions;
 using Lombiq.Tests.UI.Services;
+using System.Threading.Tasks;
 using OpenQA.Selenium;
 using OrchardCore.Users.Models;
 using Shouldly;
 using System.Threading.Tasks;
 
-namespace Lombiq.Privacy.Tests.UI.Extensions;
+namespace Lombiq.JsonEditor.Tests.UI.Extensions;
 
 public static class TestCaseUITestContextExtensions
 {
-    public static async Task TestPrivacySampleBehaviorAsync(this UITestContext context)
-    {
-        await context.ExecutePrivacySampleRecipeDirectlyAsync();
-
-        await context.TestFormConsentCheckboxContentAsync();
-
-        await context.GoToRelativeUrlAsync(AbsolutePaths.CompetitorRegistration);
-
-        // Here we fill up the competitor form.
-        await context.FillInWithRetriesAsync(By.Id("full-name"), TestCompetitor.FullName);
-        await context.FillInWithRetriesAsync(By.Id("age"), TestCompetitor.Age);
-        await context.FillInWithRetriesAsync(By.Id("e-mail"), TestCompetitor.Email);
-        await context.ClickReliablyOnSubmitAsync();
-
-        // Consent checkbox left unchecked, so after clicking on submit navigation should not happens.
-        context.GetCurrentUri().AbsolutePath.ShouldBe(AbsolutePaths.CompetitorRegistration);
-
-        // Now we set consent checkbox to checked.
-        await context.SetCheckboxValueAsync(By.Id("PrivacyConsentCheckboxPart_ConsentCheckbox"), isChecked: true);
-        await context.ClickReliablyOnSubmitAsync();
-
-        // After submit, it navigates to the new content items view.
-        context.GetCurrentUri().AbsolutePath
-            .ShouldNotBeOneOf(AbsolutePaths.ErrorPage, AbsolutePaths.CompetitorRegistration);
-    }
-
-    public static async Task TestFormConsentCheckboxContentAsync(this UITestContext context)
-    {
-        await context.GoToRelativeUrlAsync(AbsolutePaths.CompetitorRegistration);
-
-        context.VerifyElementTexts(
-            By.CssSelector(ElementSelectors.PrivacyConsentCheckboxLabelCss),
-            ExpectedContents.FormConsentCheckboxContent);
-    }
-
-    public static async Task TestConsentBannerWithThemeAsync(this UITestContext context, string theme)
-    {
-        await context.SelectThemeAsync(theme);
-        await context.SignInDirectlyAsync();
-        await context.TestConsentBannerAsync();
-    }
-
-    public static async Task TestConsentBannerAsync(this UITestContext context)
+    public static async Task TestJsonEditorSampleBehaviorAsync(this UITestContext context)
     {
-        await context.EnablePrivacyConsentBannerFeatureAsync();
-        await context.GoToHomePageAsync(onlyIfNotAlreadyThere: false);
-        await context.TestConsentBannerContentAsync();
-        await context.TestConsentBannerAcceptButtonAsync();
-    }
+        var sampleContentItemId = "4xapn6ykttkk6wbbwgg1aaxqda";
 
-    public static async Task TestConsentBannerContentAsync(this UITestContext context)
-    {
-        await context.GoToHomePageAsync();
+        await context.EnableJsonEditorFeatureAsync();
 
-        context.VerifyElementTexts(
-            By.CssSelector("#privacy-consent-banner > .modal-content > .modal-body"),
-            ExpectedContents.ConsentBannerContent);
-    }
-
-    public static async Task TestConsentBannerAcceptButtonAsync(this UITestContext context)
-    {
-        await context.GoToHomePageAsync();
+        await context.ExecuteJsonEditorSampleRecipeDirectlyAsync();
 
-        var privacyConsentAcceptButtonBy = By.Id(ElementSelectors.PrivacyConsentAcceptButtonId);
-
-        void AssertPrivacyConsentAcceptButtonMissing()
-        {
-            try
-            {
-                context.Missing(privacyConsentAcceptButtonBy);
-            }
-            catch (StaleElementReferenceException)
-            {
-                // If the banner disappears when we're in the middle of Missing() then unlucky timing can cause it to
-                // get properties of the now vanished element, causing a StaleElementReferenceException. This is not a
-                // problem, so we may continue.
-            }
-        }
-
-        await context.ClickReliablyOnAsync(privacyConsentAcceptButtonBy);
-        AssertPrivacyConsentAcceptButtonMissing();
-
-        // Verify persistence.
-        context.Refresh();
-        AssertPrivacyConsentAcceptButtonMissing();
-    }
+        // Checking if the sample item is displayed correctly.
+        await context.GoToContentItemByIdAsync(sampleContentItemId);
 
-    public static async Task TestRegistrationConsentCheckboxAsync(this UITestContext context)
-    {
-        await context.EnablePrivacyConsentBannerFeatureAsync();
-        await context.EnablePrivacyRegistrationConsentFeatureAsync();
-        await context.SetUserRegistrationTypeAsync(UserRegistrationType.AllowRegistration);
-
-        await context.TestRegistrationConsentCheckboxContentAsync();
-
-        // Go to registration and create a new user.
-        await context.GoToRegistrationPageAsync();
-        await context.FillInWithRetriesAsync(By.Id("UserName"), TestUser.Name);
-        await context.FillInWithRetriesAsync(By.Id("Email"), TestUser.Email);
-        await context.FillInWithRetriesAsync(By.Id("Password"), TestUser.Password);
-        await context.FillInWithRetriesAsync(By.Id("ConfirmPassword"), TestUser.Password);
-        await context.SetCheckboxValueAsync(By.Id("RegistrationCheckbox"), isChecked: true);
-        await context.ClickReliablyOnSubmitAsync();
-
-        // Login with the created user.
-        await context.SignInDirectlyAsync(TestUser.Name);
-        (await context.GetCurrentUserNameAsync()).ShouldBe(TestUser.Name);
-
-        // Check that, the consent banner doesn't come up after login.
-        await context.GoToHomePageAsync();
-        context.Missing(By.Id(ElementSelectors.PrivacyConsentAcceptButtonId));
-    }
-
-    public static async Task TestRegistrationConsentCheckboxContentAsync(this UITestContext context)
-    {
-        await context.GoToRegistrationPageAsync();
+        context.Exists(By.XPath($"//div[contains(text(),'These are coming from the JSON field:')]"));
+        context.Exists(By.XPath($"//li[contains(text(),'hello')]"));
+        context.Exists(By.XPath($"//li[contains(text(),'world')]"));
 
-        context.VerifyElementTexts(
-            By.CssSelector(ElementSelectors.PrivacyConsentCheckboxLabelCss),
-            ExpectedContents.RegistrationConsentCheckboxContent);
+        context.GoToContentItemEditorByIdAsync(sampleContentItemId);
     }
 }
diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs
index bd6636f..bd8e05b 100644
--- a/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs
+++ b/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs
@@ -1,36 +1,15 @@
-using Lombiq.Privacy.Constants;
-using Lombiq.Privacy.Tests.UI.Constants;
+using Lombiq.JsonEditor.Constants;
 using Lombiq.Tests.UI.Extensions;
 using Lombiq.Tests.UI.Services;
-using OpenQA.Selenium;
 using System.Threading.Tasks;
 
-namespace Lombiq.Privacy.Tests.UI.Extensions;
+namespace Lombiq.JsonEditor.Tests.UI.Extensions;
 
 public static class UITestContextExtensions
 {
-    public static Task ExecutePrivacySampleRecipeDirectlyAsync(this UITestContext context) =>
-        context.ExecuteRecipeDirectlyAsync("Lombiq.Privacy.Samples");
+    public static Task ExecuteJsonEditorSampleRecipeDirectlyAsync(this UITestContext context) =>
+        context.ExecuteRecipeDirectlyAsync("Lombiq.JsonEditor.Sample");
 
-    public static Task EnablePrivacyConsentBannerFeatureAsync(this UITestContext context) =>
-        context.EnableFeatureDirectlyAsync(FeatureNames.ConsentBanner);
-
-    public static Task EnablePrivacyRegistrationConsentFeatureAsync(this UITestContext context) =>
-        context.EnableFeatureDirectlyAsync(FeatureNames.RegistrationConsent);
-
-    /// <remarks>
-    /// <para>
-    /// This is necessary for Application Insights tracking, even in offline mode.
-    /// </para>
-    /// </remarks>
-    public static Task AcceptPrivacyConsentAsync(this UITestContext context) =>
-        context.ClickReliablyOnAsync(By.Id(ElementSelectors.PrivacyConsentAcceptButtonId));
-
-    public static async Task EnablePrivacyConsentBannerFeatureAndAcceptPrivacyConsentAsync(this UITestContext context)
-    {
-        await context.EnablePrivacyConsentBannerFeatureAsync();
-        await context.GoToHomePageAsync(onlyIfNotAlreadyThere: false);
-        await context.AcceptPrivacyConsentAsync();
-        context.Refresh();
-    }
+    public static Task EnableJsonEditorFeatureAsync(this UITestContext context) =>
+        context.EnableFeatureDirectlyAsync(FeatureIds.Default);
 }
diff --git a/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj b/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
index afdd441..4e672e1 100644
--- a/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
+++ b/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
@@ -16,6 +16,10 @@
     <PackageLicenseFile>License.md</PackageLicenseFile>
   </PropertyGroup>
 
+  <ItemGroup>
+    <ProjectReference Include="..\Lombiq.JsonEditor\Lombiq.JsonEditor.csproj" />
+  </ItemGroup>
+
   <ItemGroup Condition="'$(NuGetBuild)' != 'true'">
     <ProjectReference Include="..\..\..\..\test\Lombiq.UITestingToolbox\Lombiq.Tests.UI\Lombiq.Tests.UI.csproj" />
   </ItemGroup>

From 5b383316770bfceb3b40f61c81e9d55fb708247e Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Sat, 5 Aug 2023 19:35:12 +0200
Subject: [PATCH 03/16] Refining test(s) (WIP)

---
 .../TestCaseUITestContextExtensions.cs        | 92 +++++++++++++++++--
 1 file changed, 83 insertions(+), 9 deletions(-)

diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
index 1f84c42..428e830 100644
--- a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
+++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
@@ -1,30 +1,104 @@
+using Atata;
 using Lombiq.Tests.UI.Extensions;
 using Lombiq.Tests.UI.Services;
-using System.Threading.Tasks;
 using OpenQA.Selenium;
-using OrchardCore.Users.Models;
-using Shouldly;
 using System.Threading.Tasks;
 
 namespace Lombiq.JsonEditor.Tests.UI.Extensions;
 
 public static class TestCaseUITestContextExtensions
 {
+    private const string SampleContentItemId = "4xapn6ykttkk6wbbwgg1aaxqda";
+    private const string HelloValue = "hello";
+    private const string WorldValue = "world";
+
+    private static readonly By ObjectByXPath = By.XPath($"//div[@class='jsoneditor-readonly' and contains(text(),'object')]");
+    private static readonly By ObjectCountByXPath = By.XPath($"//div[@class='jsoneditor-value jsoneditor-object' and contains(text(),'{{1}}')]");
+    private static readonly By ArrayByXPath = By.XPath($"//div[@class='jsoneditor-field' and contains(text(),'printThese')]");
+    private static readonly By ArrayCountByXPath = By.XPath($"//div[@class='jsoneditor-value jsoneditor-array' and contains(text(),'[2]')]");
+
     public static async Task TestJsonEditorSampleBehaviorAsync(this UITestContext context)
     {
-        var sampleContentItemId = "4xapn6ykttkk6wbbwgg1aaxqda";
-
         await context.EnableJsonEditorFeatureAsync();
 
         await context.ExecuteJsonEditorSampleRecipeDirectlyAsync();
 
         // Checking if the sample item is displayed correctly.
-        await context.GoToContentItemByIdAsync(sampleContentItemId);
+        await context.GoToContentItemByIdAsync(SampleContentItemId);
 
         context.Exists(By.XPath($"//div[contains(text(),'These are coming from the JSON field:')]"));
-        context.Exists(By.XPath($"//li[contains(text(),'hello')]"));
-        context.Exists(By.XPath($"//li[contains(text(),'world')]"));
+        context.Exists(By.XPath($"//li[contains(text(),'{HelloValue}')]"));
+        context.Exists(By.XPath($"//li[contains(text(),'{WorldValue}')]"));
+
+        await context.SignInDirectlyAsync();
+        await context.GoToContentItemEditorByIdAsync(SampleContentItemId);
+
+        // Checking if the sample item is displayed correctly in the editor in tree editor mode.
+        await context.TestTreeTypeModeAsync();
+
+        // Checking if the sample item is displayed correctly in the editor in tree view mode.
+        await context.SwitchToModeAsync("Switch to tree view");
+        await context.TestTreeTypeModeAsync();
 
-        context.GoToContentItemEditorByIdAsync(sampleContentItemId);
+        // Checking if the sample item is displayed correctly in the editor in form editor mode.
+        await context.SwitchToModeAsync("Switch to form editor");
+        await context.TestTreeTypeModeAsync();
     }
+
+    private static void CheckArrayValueInTreeMode(this UITestContext context, string arrayValue, bool missing = false)
+    {
+        var arrayValueByXPath =
+            By.XPath($"//div[@class='jsoneditor-value jsoneditor-string' and contains(text(),'{arrayValue}')]");
+
+        if (!missing)
+        {
+            context.Exists(arrayValueByXPath);
+        }
+        else
+        {
+            context.Missing(arrayValueByXPath);
+        }
+    }
+
+    private static Task ClickOnExpandAllAsync(this UITestContext context) =>
+        context.ClickReliablyOnAsync(By.XPath($"//button[@class='jsoneditor-expand-all']"));
+
+    private static Task ClickOnCollapseAllAsync(this UITestContext context) =>
+        context.ClickReliablyOnAsync(By.XPath($"//button[@class='jsoneditor-collapse-all']"));
+
+    private static async Task SwitchToModeAsync(this UITestContext context, string editorTitle)
+    {
+        await context.ClickReliablyOnAsync(By.XPath($"//button[@class='jsoneditor-modes jsoneditor-separator']"));
+        await context.ClickReliablyOnAsync(By.XPath($"//button[@class='jsoneditor-type-modes' and @title ='{editorTitle}']"));
+    }
+
+    private static async Task TestTreeTypeModeAsync(this UITestContext context)
+    {
+        await context.ClickOnExpandAllAsync();
+
+        // Checking object {1}.
+        context.Exists(ObjectByXPath);
+        context.Exists(ObjectCountByXPath);
+
+        // Checking printThese [2].
+        context.Exists(ArrayByXPath);
+        context.Exists(ArrayCountByXPath);
+
+        // Checking "hello" and "word".
+        context.CheckArrayValueInTreeMode(HelloValue);
+        context.CheckArrayValueInTreeMode(WorldValue);
+
+        // Collapse button should hide things.
+        await context.ClickOnCollapseAllAsync();
+
+        context.Exists(ObjectByXPath);
+        context.Exists(ObjectCountByXPath);
+
+        context.Missing(ArrayByXPath);
+        context.Missing(ArrayCountByXPath);
+
+        context.CheckArrayValueInTreeMode(HelloValue, missing: true);
+        context.CheckArrayValueInTreeMode(WorldValue, missing: true);
+    }
+
 }

From d7cfa61df8438e1435406e28b51ccb21691bc77c Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Sat, 5 Aug 2023 19:35:57 +0200
Subject: [PATCH 04/16] Removing unused Const folder

---
 Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs | 7 -------
 1 file changed, 7 deletions(-)
 delete mode 100644 Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs

diff --git a/Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs b/Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs
deleted file mode 100644
index 80a7565..0000000
--- a/Lombiq.JsonEditor.Test.UI/Constants/ElementSelectors.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Lombiq.JsonEditor.Tests.UI.Constants;
-
-public static class ElementSelectors
-{
-    public const string PrivacyConsentAcceptButtonId = "privacy-consent-accept-button";
-    public const string PrivacyConsentCheckboxLabelCss = "div.form-check > label";
-}

From f1f7494535901ab8547c051b1a75a3f64a9ea414 Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Sat, 5 Aug 2023 23:13:56 +0200
Subject: [PATCH 05/16] Consolidating packages

---
 Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj b/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
index 4e672e1..0df031f 100644
--- a/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
+++ b/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
@@ -25,7 +25,7 @@
   </ItemGroup>
 
   <ItemGroup Condition="'$(NuGetBuild)' == 'true'">
-    <PackageReference Include="Lombiq.Tests.UI" Version="7.0.0" />
+    <PackageReference Include="Lombiq.Tests.UI" Version="7.1.0" />
   </ItemGroup>
 
   <ItemGroup>

From 181f7b66e4af64883f122d481740320e60e2e171 Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Sun, 6 Aug 2023 00:49:44 +0200
Subject: [PATCH 06/16] Also testing if input is saved

---
 .../TestCaseUITestContextExtensions.cs        | 73 ++++++++++++++-----
 1 file changed, 55 insertions(+), 18 deletions(-)

diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
index 428e830..adc244f 100644
--- a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
+++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
@@ -1,7 +1,9 @@
 using Atata;
 using Lombiq.Tests.UI.Extensions;
 using Lombiq.Tests.UI.Services;
+using Newtonsoft.Json.Linq;
 using OpenQA.Selenium;
+using Shouldly;
 using System.Threading.Tasks;
 
 namespace Lombiq.JsonEditor.Tests.UI.Extensions;
@@ -11,11 +13,14 @@ public static class TestCaseUITestContextExtensions
     private const string SampleContentItemId = "4xapn6ykttkk6wbbwgg1aaxqda";
     private const string HelloValue = "hello";
     private const string WorldValue = "world";
+    private const string TestField = "testField";
+    private const string TestValue = "testValue";
 
     private static readonly By ObjectByXPath = By.XPath($"//div[@class='jsoneditor-readonly' and contains(text(),'object')]");
-    private static readonly By ObjectCountByXPath = By.XPath($"//div[@class='jsoneditor-value jsoneditor-object' and contains(text(),'{{1}}')]");
+    private static readonly By ObjectCountByXPath = By.XPath($"//div[@class='jsoneditor-value jsoneditor-object' and contains(text(),'{{2}}')]");
     private static readonly By ArrayByXPath = By.XPath($"//div[@class='jsoneditor-field' and contains(text(),'printThese')]");
     private static readonly By ArrayCountByXPath = By.XPath($"//div[@class='jsoneditor-value jsoneditor-array' and contains(text(),'[2]')]");
+    private static readonly By FieldByXPath = By.XPath($"//div[@class='jsoneditor-field' and contains(text(), '{TestField}')]");
 
     public static async Task TestJsonEditorSampleBehaviorAsync(this UITestContext context)
     {
@@ -33,19 +38,35 @@ public static async Task TestJsonEditorSampleBehaviorAsync(this UITestContext co
         await context.SignInDirectlyAsync();
         await context.GoToContentItemEditorByIdAsync(SampleContentItemId);
 
-        // Checking if the sample item is displayed correctly in the editor in tree editor mode.
-        await context.TestTreeTypeModeAsync();
+        // Testing if input is saved.
+        context.GetAll(By.XPath($"//button[@class='jsoneditor-button jsoneditor-contextmenu-button']"))[1].Click();
+        await context.ClickReliablyOnAsync(By.XPath($"//div[contains(text(),'Append')]"));
 
-        // Checking if the sample item is displayed correctly in the editor in tree view mode.
-        await context.SwitchToModeAsync("Switch to tree view");
-        await context.TestTreeTypeModeAsync();
+        context.Get(By.XPath($"//div[@class='jsoneditor-field jsoneditor-empty']")).FillInWith(TestField);
+        context.Get(By.XPath($"//div[@class='jsoneditor-value jsoneditor-string jsoneditor-empty']")).FillInWith(TestValue);
+        await context.ClickPublishAsync();
 
-        // Checking if the sample item is displayed correctly in the editor in form editor mode.
-        await context.SwitchToModeAsync("Switch to form editor");
-        await context.TestTreeTypeModeAsync();
+        // Checking if the sample item is displayed correctly in all tree style mode.
+        await context.TestTreeStyleModeAsync();
+
+        await context.SwitchToModeAsync("View");
+        await context.TestTreeStyleModeAsync();
+
+        await context.SwitchToModeAsync("Form");
+        await context.TestTreeStyleModeAsync();
+
+        // Checking if the sample item is displayed correctly in all code style mode.
+        await context.SwitchToModeAsync("Code");
+        context.TestCodeStyleMode();
+
+        await context.SwitchToModeAsync("Text");
+        context.TestCodeStyleMode();
+
+        await context.SwitchToModeAsync("Preview");
+        context.TestCodeStyleMode();
     }
 
-    private static void CheckArrayValueInTreeMode(this UITestContext context, string arrayValue, bool missing = false)
+    private static void CheckValueInTreeMode(this UITestContext context, string arrayValue, bool missing = false)
     {
         var arrayValueByXPath =
             By.XPath($"//div[@class='jsoneditor-value jsoneditor-string' and contains(text(),'{arrayValue}')]");
@@ -66,13 +87,13 @@ private static Task ClickOnExpandAllAsync(this UITestContext context) =>
     private static Task ClickOnCollapseAllAsync(this UITestContext context) =>
         context.ClickReliablyOnAsync(By.XPath($"//button[@class='jsoneditor-collapse-all']"));
 
-    private static async Task SwitchToModeAsync(this UITestContext context, string editorTitle)
+    private static async Task SwitchToModeAsync(this UITestContext context, string editorName)
     {
         await context.ClickReliablyOnAsync(By.XPath($"//button[@class='jsoneditor-modes jsoneditor-separator']"));
-        await context.ClickReliablyOnAsync(By.XPath($"//button[@class='jsoneditor-type-modes' and @title ='{editorTitle}']"));
+        await context.ClickReliablyOnAsync(By.XPath($"//div[@class='jsoneditor-text' and contains(text(),'{editorName}')]"));
     }
 
-    private static async Task TestTreeTypeModeAsync(this UITestContext context)
+    private static async Task TestTreeStyleModeAsync(this UITestContext context)
     {
         await context.ClickOnExpandAllAsync();
 
@@ -84,9 +105,12 @@ private static async Task TestTreeTypeModeAsync(this UITestContext context)
         context.Exists(ArrayByXPath);
         context.Exists(ArrayCountByXPath);
 
-        // Checking "hello" and "word".
-        context.CheckArrayValueInTreeMode(HelloValue);
-        context.CheckArrayValueInTreeMode(WorldValue);
+        context.Exists(FieldByXPath);
+
+        // Checking "hello" and "word" and "testValue".
+        context.CheckValueInTreeMode(HelloValue);
+        context.CheckValueInTreeMode(WorldValue);
+        context.CheckValueInTreeMode(TestValue);
 
         // Collapse button should hide things.
         await context.ClickOnCollapseAllAsync();
@@ -97,8 +121,21 @@ private static async Task TestTreeTypeModeAsync(this UITestContext context)
         context.Missing(ArrayByXPath);
         context.Missing(ArrayCountByXPath);
 
-        context.CheckArrayValueInTreeMode(HelloValue, missing: true);
-        context.CheckArrayValueInTreeMode(WorldValue, missing: true);
+        context.Missing(FieldByXPath);
+
+        context.CheckValueInTreeMode(HelloValue, missing: true);
+        context.CheckValueInTreeMode(WorldValue, missing: true);
+        context.CheckValueInTreeMode(TestValue, missing: true);
     }
 
+    private static void TestCodeStyleMode(this UITestContext context)
+    {
+        // This field is hidden but it's content reflects what's in the editor.
+        var editorContent = JObject.Parse(context.Get(By.XPath($"//input[@class='jsonEditor__input']").OfAnyVisibility())
+        .GetValue());
+
+        ((string)editorContent["printThese"][0]).ShouldBe(HelloValue);
+        ((string)editorContent["printThese"][1]).ShouldBe(WorldValue);
+        ((string)editorContent[TestField]).ShouldBe(TestValue);
+    }
 }

From 945bb58c7f21eb9fb7a445531bb8aba022ee3a7c Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Sun, 6 Aug 2023 00:59:45 +0200
Subject: [PATCH 07/16] Code styling, comment correction

---
 .../Extensions/TestCaseUITestContextExtensions.cs          | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
index adc244f..0d71642 100644
--- a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
+++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
@@ -130,9 +130,10 @@ private static async Task TestTreeStyleModeAsync(this UITestContext context)
 
     private static void TestCodeStyleMode(this UITestContext context)
     {
-        // This field is hidden but it's content reflects what's in the editor.
-        var editorContent = JObject.Parse(context.Get(By.XPath($"//input[@class='jsonEditor__input']").OfAnyVisibility())
-        .GetValue());
+        // This field is hidden, but its content reflects what's in the editor.
+        var editorContent = JObject
+            .Parse(context.Get(By.XPath($"//input[@class='jsonEditor__input']").OfAnyVisibility())
+            .GetValue());
 
         ((string)editorContent["printThese"][0]).ShouldBe(HelloValue);
         ((string)editorContent["printThese"][1]).ShouldBe(WorldValue);

From 6f94cbc80e5b440d71745cd334af55a68e1bd7c4 Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Sun, 6 Aug 2023 01:03:52 +0200
Subject: [PATCH 08/16] Renaming function

---
 .../Extensions/TestCaseUITestContextExtensions.cs               | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
index 0d71642..6c8c8ac 100644
--- a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
+++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
@@ -22,7 +22,7 @@ public static class TestCaseUITestContextExtensions
     private static readonly By ArrayCountByXPath = By.XPath($"//div[@class='jsoneditor-value jsoneditor-array' and contains(text(),'[2]')]");
     private static readonly By FieldByXPath = By.XPath($"//div[@class='jsoneditor-field' and contains(text(), '{TestField}')]");
 
-    public static async Task TestJsonEditorSampleBehaviorAsync(this UITestContext context)
+    public static async Task TestJsonEditorBehaviorAsync(this UITestContext context)
     {
         await context.EnableJsonEditorFeatureAsync();
 

From 2ae7940fbe8b7981dac133ebab24716cc92a01cf Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Sun, 6 Aug 2023 01:36:53 +0200
Subject: [PATCH 09/16] Referencing Tests.UI project in solution

---
 .editorconfig                                 | 310 ++++++++++++++++++
 .../Extensions/UITestContextExtensions.cs     |   3 +-
 .../Lombiq.JsonEditor.Tests.UI.csproj         |   4 -
 Lombiq.JsonEditor.sln                         |   8 +-
 4 files changed, 318 insertions(+), 7 deletions(-)
 create mode 100644 .editorconfig

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..1791262
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,310 @@
+# WARNING: Only edit this file in the Lombiq .NET Analyzers repository's "Lombiq.Analyzers" folder. A copy of this file
+# anywhere else will be overwritten.
+
+# Common .NET conventions, code formatting and naming convention rules. Check out possible configs here:
+# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
+# If you found some issue then try to fix it, which you can also do from VS Quick Actions:
+# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#automatically-configure-code-styles
+# Add new file types at the bottom. For new rules for C# files follow the structure that VS generates.
+
+# All files
+[*]
+
+# Basics
+charset = utf-8
+guidelines = 120 1px solid a0ffc000, 150 1px solid 80ff0000
+
+# Indentation and spacing
+indent_size = 4
+indent_style = space
+trim_trailing_whitespace = true
+
+
+# New line preferences
+end_of_line = crlf
+insert_final_newline = true
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_null_propagation = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+
+# C# files
+[*.cs]
+
+#### .NET Coding Conventions ####
+
+# Organize usings
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = false
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:warning
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_property = false:warning
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:warning
+dotnet_style_predefined_type_for_member_access = true:warning
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = always:warning
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_collection_initializer = true:warning
+dotnet_style_explicit_tuple_names = true:warning
+dotnet_style_null_propagation = true:warning
+dotnet_style_object_initializer = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_compound_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_return = false
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
+dotnet_style_prefer_inferred_tuple_names = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_simplified_interpolation = true:warning
+
+# Field preferences
+dotnet_style_readonly_field = true:warning
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = non_pubic:suggestion
+
+#### C# Style Rules ####
+
+#IDE* rules are managed here.
+
+# Note that currently both IDE* rules and csharp_style_* rules are necessary, because only IDE rules will be enforced
+# during build, see: https://github.com/dotnet/roslyn/issues/44201.
+
+# Default severity for analyzer diagnostics for all categories (escalated to build warnings). See
+# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/categories for the list of categories.
+dotnet_analyzer_diagnostic.category-Design.severity = warning
+dotnet_analyzer_diagnostic.category-Documentation.severity = warning
+dotnet_analyzer_diagnostic.category-Globalization.severity = warning
+dotnet_analyzer_diagnostic.category-Interoperability.severity = warning
+dotnet_analyzer_diagnostic.category-Maintainability.severity = warning
+dotnet_analyzer_diagnostic.category-Naming.severity = warning
+dotnet_analyzer_diagnostic.category-Performance.severity = warning
+dotnet_analyzer_diagnostic.category-SingleFile.severity = warning
+dotnet_analyzer_diagnostic.category-Reliability.severity = warning
+dotnet_analyzer_diagnostic.category-Security.severity = warning
+dotnet_analyzer_diagnostic.category-Style.severity = warning
+dotnet_analyzer_diagnostic.category-Usage.severity = warning
+dotnet_analyzer_diagnostic.category-CodeQuality.severity = warning
+
+# IDE0011: Add braces to 'if' statement.
+# The "when-multiline:warning" config is not actually for cases when the if body is in another line so we have to turn
+# this off completely, see: https://github.com/dotnet/roslyn/issues/40912.
+dotnet_diagnostic.IDE0011.severity = none
+# IDE0050: Convert to tuple
+# Quite dangerous as we most frequently use anonymous types to interface with other APIs (like generating routes) and
+# those can fail on this silently in runtime while building correctly.
+dotnet_diagnostic.IDE0050.severity = none
+# IDE0052: Private member can be removed as the value assigned to it is never used.
+# We use S4487 for that.
+dotnet_diagnostic.IDE0052.severity = none
+# IDE0072: Populate switch
+# Also signals on switches with discards, by design: https://github.com/dotnet/roslyn/issues/48876.
+dotnet_diagnostic.IDE0072.severity = none
+# "Namespace 'Foo' does not match folder structure, expected 'Foo'"
+dotnet_diagnostic.IDE0130.severity = none
+
+# While these are in the Style category, they need to be explicitly set for some reason.
+# IDE0079 Remove unnecessary suppression
+dotnet_diagnostic.IDE0079.severity = warning
+dotnet_diagnostic.IDE0022.severity = warning
+
+# 'var' preferences
+dotnet_diagnostic.IDE0007.severity = warning
+dotnet_diagnostic.IDE0008.severity = none
+
+# We don't want to decorate every public member with XML comments, only public APIs - so turn CS1591 off.
+# CS1591 Missing XML Comment for publicly visible type or member
+dotnet_diagnostic.CS1591.severity = none
+
+#### C# Coding Conventions ####
+
+# 'var' preferences
+# These won't take effect during build due to this bug:
+# https://github.com/dotnet/roslyn/issues/44250
+csharp_style_var_elsewhere = true:warning
+# If this is not turned off then there will be messages for e.g. integers too, like in for loops.
+csharp_style_var_for_built_in_types = false:none
+csharp_style_var_when_type_is_apparent = true:warning
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:warning
+csharp_style_expression_bodied_constructors = true:warning
+csharp_style_expression_bodied_indexers = true:warning
+csharp_style_expression_bodied_lambdas = true:warning
+csharp_style_expression_bodied_local_functions = true:warning
+csharp_style_expression_bodied_methods = true:warning
+csharp_style_expression_bodied_operators = true:warning
+csharp_style_expression_bodied_properties = true:warning
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+csharp_style_prefer_switch_expression = true:warning
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:warning
+
+# Modifier preferences
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
+
+# Code-block preferences
+csharp_prefer_simple_using_statement = true:warning
+# IDE0160: Convert to block-scoped namespace
+csharp_style_namespace_declarations = file_scoped:warning
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:warning
+csharp_style_deconstructed_variable_declaration = false:silent
+csharp_style_inlined_variable_declaration = true:warning
+csharp_style_pattern_local_over_anonymous_function = true:warning
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
+csharp_style_throw_expression = true:warning
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:none
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:silent
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = no_change
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = *
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = *
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = *
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+# Disabling "catch a more specific exception type" suggestion which is overwhelmingly a false positive.
+dotnet_diagnostic.CA1031.severity = silent
+# Disabling "do not nest type" suggestion.
+dotnet_diagnostic.CA1034.severity = silent
+# Disabling "CA1810: Initialize reference type static fields inline" as RSPEC-3963 is used instead.
+dotnet_diagnostic.CA1810.severity = none
+
+# Don't apply "CA1822: Mark members as static" to the public API
+dotnet_code_quality.ca1822.api_surface = private, internal
+csharp_prefer_braces = true:silent
+
+# Various config files
+[*.{config,csproj,json,props,targets}]
+
+indent_size = 2
+
+# Markdown files
+[*.md]
+
+trim_trailing_whitespace = false
+
+# JavaScript files
+[*.js]
+
+# Placeholder, no unique rules for JS files at the moment.
+
+
+# SCSS files
+[*.scss]
+
+# Placeholder, no unique rules for SCSS files at the moment.
+
+# PowerShell files
+[*.ps1]
+
+# Placeholder, no unique rules for PS files at the moment.
diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs
index bd8e05b..37560eb 100644
--- a/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs
+++ b/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs
@@ -1,4 +1,3 @@
-using Lombiq.JsonEditor.Constants;
 using Lombiq.Tests.UI.Extensions;
 using Lombiq.Tests.UI.Services;
 using System.Threading.Tasks;
@@ -11,5 +10,5 @@ public static Task ExecuteJsonEditorSampleRecipeDirectlyAsync(this UITestContext
         context.ExecuteRecipeDirectlyAsync("Lombiq.JsonEditor.Sample");
 
     public static Task EnableJsonEditorFeatureAsync(this UITestContext context) =>
-        context.EnableFeatureDirectlyAsync(FeatureIds.Default);
+        context.EnableFeatureDirectlyAsync("Lombiq.JsonEditor");
 }
diff --git a/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj b/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
index 0df031f..076b630 100644
--- a/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
+++ b/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
@@ -16,10 +16,6 @@
     <PackageLicenseFile>License.md</PackageLicenseFile>
   </PropertyGroup>
 
-  <ItemGroup>
-    <ProjectReference Include="..\Lombiq.JsonEditor\Lombiq.JsonEditor.csproj" />
-  </ItemGroup>
-
   <ItemGroup Condition="'$(NuGetBuild)' != 'true'">
     <ProjectReference Include="..\..\..\..\test\Lombiq.UITestingToolbox\Lombiq.Tests.UI\Lombiq.Tests.UI.csproj" />
   </ItemGroup>
diff --git a/Lombiq.JsonEditor.sln b/Lombiq.JsonEditor.sln
index 81e2879..4e458c4 100644
--- a/Lombiq.JsonEditor.sln
+++ b/Lombiq.JsonEditor.sln
@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio Version 17
 VisualStudioVersion = 17.6.33815.320
 MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lombiq.JsonEditor", "Lombiq.JsonEditor\Lombiq.JsonEditor.csproj", "{3EA5BC81-97A5-4513-9CAC-4DB01B439682}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.JsonEditor", "Lombiq.JsonEditor\Lombiq.JsonEditor.csproj", "{3EA5BC81-97A5-4513-9CAC-4DB01B439682}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.JsonEditor.Tests.UI", "Lombiq.JsonEditor.Test.UI\Lombiq.JsonEditor.Tests.UI.csproj", "{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,6 +17,10 @@ Global
 		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Release|Any CPU.Build.0 = Release|Any CPU
+		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

From 5370581ee97422a358f541546251dc846300ab54 Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Sun, 6 Aug 2023 01:46:39 +0200
Subject: [PATCH 10/16] Linking Tests UI project in readme

---
 Readme.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Readme.md b/Readme.md
index 9461c46..8d2bcd7 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,6 +1,6 @@
 # Lombiq JSON Editor for Orchard Core
 
-[![Lombiq.JsonEditor NuGet](https://img.shields.io/nuget/v/Lombiq.JsonEditor?label=Lombiq.JsonEditor)](https://www.nuget.org/packages/Lombiq.JsonEditor/)
+[![Lombiq.JsonEditor NuGet](https://img.shields.io/nuget/v/Lombiq.JsonEditor?label=Lombiq.JsonEditor)](https://www.nuget.org/packages/Lombiq.JsonEditor/) [![Lombiq.JsonEditor.Tests.UI NuGet](https://img.shields.io/nuget/v/Lombiq.JsonEditor.Tests.UI?label=Lombiq.JsonEditor.Tests.UI)](https://www.nuget.org/packages/Lombiq.JsonEditor.Tests.UI/)
 
 ## About
 

From 68174d2c5491e62030b46eea4f6de71473cf8fff Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Tue, 8 Aug 2023 15:51:32 +0200
Subject: [PATCH 11/16] Removing .editorconfig files, replacing Licenses

---
 .editorconfig                                 | 310 ------------------
 Lombiq.JsonEditor.Test.UI/License.md          |   2 +-
 .../Lombiq.JsonEditor.Tests.UI.csproj         |   2 +-
 Lombiq.JsonEditor.sln                         |  31 --
 Lombiq.JsonEditor/.editorconfig               | 310 ------------------
 5 files changed, 2 insertions(+), 653 deletions(-)
 delete mode 100644 .editorconfig
 delete mode 100644 Lombiq.JsonEditor.sln
 delete mode 100644 Lombiq.JsonEditor/.editorconfig

diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 1791262..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,310 +0,0 @@
-# WARNING: Only edit this file in the Lombiq .NET Analyzers repository's "Lombiq.Analyzers" folder. A copy of this file
-# anywhere else will be overwritten.
-
-# Common .NET conventions, code formatting and naming convention rules. Check out possible configs here:
-# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
-# If you found some issue then try to fix it, which you can also do from VS Quick Actions:
-# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#automatically-configure-code-styles
-# Add new file types at the bottom. For new rules for C# files follow the structure that VS generates.
-
-# All files
-[*]
-
-# Basics
-charset = utf-8
-guidelines = 120 1px solid a0ffc000, 150 1px solid 80ff0000
-
-# Indentation and spacing
-indent_size = 4
-indent_style = space
-trim_trailing_whitespace = true
-
-
-# New line preferences
-end_of_line = crlf
-insert_final_newline = true
-dotnet_style_operator_placement_when_wrapping = beginning_of_line
-tab_width = 4
-dotnet_style_coalesce_expression = true:warning
-dotnet_style_null_propagation = true:warning
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
-dotnet_style_prefer_auto_properties = true:warning
-
-# C# files
-[*.cs]
-
-#### .NET Coding Conventions ####
-
-# Organize usings
-dotnet_separate_import_directive_groups = false
-dotnet_sort_system_directives_first = false
-
-# this. and Me. preferences
-dotnet_style_qualification_for_event = false:warning
-dotnet_style_qualification_for_field = false:warning
-dotnet_style_qualification_for_method = false:warning
-dotnet_style_qualification_for_property = false:warning
-
-# Language keywords vs BCL types preferences
-dotnet_style_predefined_type_for_locals_parameters_members = true:warning
-dotnet_style_predefined_type_for_member_access = true:warning
-
-# Parentheses preferences
-dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
-dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
-dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
-dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
-
-# Modifier preferences
-dotnet_style_require_accessibility_modifiers = always:warning
-
-# Expression-level preferences
-dotnet_style_coalesce_expression = true:warning
-dotnet_style_collection_initializer = true:warning
-dotnet_style_explicit_tuple_names = true:warning
-dotnet_style_null_propagation = true:warning
-dotnet_style_object_initializer = true:warning
-dotnet_style_prefer_auto_properties = true:warning
-dotnet_style_prefer_compound_assignment = true:warning
-dotnet_style_prefer_conditional_expression_over_assignment = true:warning
-dotnet_style_prefer_conditional_expression_over_return = false
-dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
-dotnet_style_prefer_inferred_tuple_names = true:warning
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
-dotnet_style_prefer_simplified_interpolation = true:warning
-
-# Field preferences
-dotnet_style_readonly_field = true:warning
-
-# Parameter preferences
-dotnet_code_quality_unused_parameters = non_pubic:suggestion
-
-#### C# Style Rules ####
-
-#IDE* rules are managed here.
-
-# Note that currently both IDE* rules and csharp_style_* rules are necessary, because only IDE rules will be enforced
-# during build, see: https://github.com/dotnet/roslyn/issues/44201.
-
-# Default severity for analyzer diagnostics for all categories (escalated to build warnings). See
-# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/categories for the list of categories.
-dotnet_analyzer_diagnostic.category-Design.severity = warning
-dotnet_analyzer_diagnostic.category-Documentation.severity = warning
-dotnet_analyzer_diagnostic.category-Globalization.severity = warning
-dotnet_analyzer_diagnostic.category-Interoperability.severity = warning
-dotnet_analyzer_diagnostic.category-Maintainability.severity = warning
-dotnet_analyzer_diagnostic.category-Naming.severity = warning
-dotnet_analyzer_diagnostic.category-Performance.severity = warning
-dotnet_analyzer_diagnostic.category-SingleFile.severity = warning
-dotnet_analyzer_diagnostic.category-Reliability.severity = warning
-dotnet_analyzer_diagnostic.category-Security.severity = warning
-dotnet_analyzer_diagnostic.category-Style.severity = warning
-dotnet_analyzer_diagnostic.category-Usage.severity = warning
-dotnet_analyzer_diagnostic.category-CodeQuality.severity = warning
-
-# IDE0011: Add braces to 'if' statement.
-# The "when-multiline:warning" config is not actually for cases when the if body is in another line so we have to turn
-# this off completely, see: https://github.com/dotnet/roslyn/issues/40912.
-dotnet_diagnostic.IDE0011.severity = none
-# IDE0050: Convert to tuple
-# Quite dangerous as we most frequently use anonymous types to interface with other APIs (like generating routes) and
-# those can fail on this silently in runtime while building correctly.
-dotnet_diagnostic.IDE0050.severity = none
-# IDE0052: Private member can be removed as the value assigned to it is never used.
-# We use S4487 for that.
-dotnet_diagnostic.IDE0052.severity = none
-# IDE0072: Populate switch
-# Also signals on switches with discards, by design: https://github.com/dotnet/roslyn/issues/48876.
-dotnet_diagnostic.IDE0072.severity = none
-# "Namespace 'Foo' does not match folder structure, expected 'Foo'"
-dotnet_diagnostic.IDE0130.severity = none
-
-# While these are in the Style category, they need to be explicitly set for some reason.
-# IDE0079 Remove unnecessary suppression
-dotnet_diagnostic.IDE0079.severity = warning
-dotnet_diagnostic.IDE0022.severity = warning
-
-# 'var' preferences
-dotnet_diagnostic.IDE0007.severity = warning
-dotnet_diagnostic.IDE0008.severity = none
-
-# We don't want to decorate every public member with XML comments, only public APIs - so turn CS1591 off.
-# CS1591 Missing XML Comment for publicly visible type or member
-dotnet_diagnostic.CS1591.severity = none
-
-#### C# Coding Conventions ####
-
-# 'var' preferences
-# These won't take effect during build due to this bug:
-# https://github.com/dotnet/roslyn/issues/44250
-csharp_style_var_elsewhere = true:warning
-# If this is not turned off then there will be messages for e.g. integers too, like in for loops.
-csharp_style_var_for_built_in_types = false:none
-csharp_style_var_when_type_is_apparent = true:warning
-
-# Expression-bodied members
-csharp_style_expression_bodied_accessors = true:warning
-csharp_style_expression_bodied_constructors = true:warning
-csharp_style_expression_bodied_indexers = true:warning
-csharp_style_expression_bodied_lambdas = true:warning
-csharp_style_expression_bodied_local_functions = true:warning
-csharp_style_expression_bodied_methods = true:warning
-csharp_style_expression_bodied_operators = true:warning
-csharp_style_expression_bodied_properties = true:warning
-
-# Pattern matching preferences
-csharp_style_pattern_matching_over_as_with_null_check = true:warning
-csharp_style_pattern_matching_over_is_with_cast_check = true:warning
-csharp_style_prefer_switch_expression = true:warning
-
-# Null-checking preferences
-csharp_style_conditional_delegate_call = true:warning
-
-# Modifier preferences
-csharp_prefer_static_local_function = true:warning
-csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
-
-# Code-block preferences
-csharp_prefer_simple_using_statement = true:warning
-# IDE0160: Convert to block-scoped namespace
-csharp_style_namespace_declarations = file_scoped:warning
-
-# Expression-level preferences
-csharp_prefer_simple_default_expression = true:warning
-csharp_style_deconstructed_variable_declaration = false:silent
-csharp_style_inlined_variable_declaration = true:warning
-csharp_style_pattern_local_over_anonymous_function = true:warning
-csharp_style_prefer_index_operator = true:warning
-csharp_style_prefer_range_operator = true:warning
-csharp_style_throw_expression = true:warning
-csharp_style_unused_value_assignment_preference = discard_variable:suggestion
-csharp_style_unused_value_expression_statement_preference = discard_variable:none
-
-# 'using' directive preferences
-csharp_using_directive_placement = outside_namespace:silent
-
-#### C# Formatting Rules ####
-
-# New line preferences
-csharp_new_line_before_catch = true
-csharp_new_line_before_else = true
-csharp_new_line_before_finally = true
-csharp_new_line_before_members_in_anonymous_types = true
-csharp_new_line_before_members_in_object_initializers = true
-csharp_new_line_before_open_brace = all
-csharp_new_line_between_query_expression_clauses = true
-
-# Indentation preferences
-csharp_indent_block_contents = true
-csharp_indent_braces = false
-csharp_indent_case_contents = true
-csharp_indent_case_contents_when_block = true
-csharp_indent_labels = no_change
-csharp_indent_switch_labels = true
-
-# Space preferences
-csharp_space_after_cast = false
-csharp_space_after_colon_in_inheritance_clause = true
-csharp_space_after_comma = true
-csharp_space_after_dot = false
-csharp_space_after_keywords_in_control_flow_statements = true
-csharp_space_after_semicolon_in_for_statement = true
-csharp_space_around_binary_operators = before_and_after
-csharp_space_around_declaration_statements = false
-csharp_space_before_colon_in_inheritance_clause = true
-csharp_space_before_comma = false
-csharp_space_before_dot = false
-csharp_space_before_open_square_brackets = false
-csharp_space_before_semicolon_in_for_statement = false
-csharp_space_between_empty_square_brackets = false
-csharp_space_between_method_call_empty_parameter_list_parentheses = false
-csharp_space_between_method_call_name_and_opening_parenthesis = false
-csharp_space_between_method_call_parameter_list_parentheses = false
-csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
-csharp_space_between_method_declaration_name_and_open_parenthesis = false
-csharp_space_between_method_declaration_parameter_list_parentheses = false
-csharp_space_between_parentheses = false
-csharp_space_between_square_brackets = false
-
-# Wrapping preferences
-csharp_preserve_single_line_blocks = true
-csharp_preserve_single_line_statements = true
-
-#### Naming styles ####
-
-# Naming rules
-
-dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
-dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
-dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
-
-dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
-dotnet_naming_rule.types_should_be_pascal_case.symbols = types
-dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
-
-dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
-dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
-dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
-
-# Symbol specifications
-
-dotnet_naming_symbols.interface.applicable_kinds = interface
-dotnet_naming_symbols.interface.applicable_accessibilities = *
-dotnet_naming_symbols.interface.required_modifiers =
-
-dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
-dotnet_naming_symbols.types.applicable_accessibilities = *
-dotnet_naming_symbols.types.required_modifiers =
-
-dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
-dotnet_naming_symbols.non_field_members.applicable_accessibilities = *
-dotnet_naming_symbols.non_field_members.required_modifiers =
-
-# Naming styles
-
-dotnet_naming_style.pascal_case.required_prefix =
-dotnet_naming_style.pascal_case.required_suffix =
-dotnet_naming_style.pascal_case.word_separator =
-dotnet_naming_style.pascal_case.capitalization = pascal_case
-
-dotnet_naming_style.begins_with_i.required_prefix = I
-dotnet_naming_style.begins_with_i.required_suffix =
-dotnet_naming_style.begins_with_i.word_separator =
-dotnet_naming_style.begins_with_i.capitalization = pascal_case
-
-# Disabling "catch a more specific exception type" suggestion which is overwhelmingly a false positive.
-dotnet_diagnostic.CA1031.severity = silent
-# Disabling "do not nest type" suggestion.
-dotnet_diagnostic.CA1034.severity = silent
-# Disabling "CA1810: Initialize reference type static fields inline" as RSPEC-3963 is used instead.
-dotnet_diagnostic.CA1810.severity = none
-
-# Don't apply "CA1822: Mark members as static" to the public API
-dotnet_code_quality.ca1822.api_surface = private, internal
-csharp_prefer_braces = true:silent
-
-# Various config files
-[*.{config,csproj,json,props,targets}]
-
-indent_size = 2
-
-# Markdown files
-[*.md]
-
-trim_trailing_whitespace = false
-
-# JavaScript files
-[*.js]
-
-# Placeholder, no unique rules for JS files at the moment.
-
-
-# SCSS files
-[*.scss]
-
-# Placeholder, no unique rules for SCSS files at the moment.
-
-# PowerShell files
-[*.ps1]
-
-# Placeholder, no unique rules for PS files at the moment.
diff --git a/Lombiq.JsonEditor.Test.UI/License.md b/Lombiq.JsonEditor.Test.UI/License.md
index 4add106..d57e130 100644
--- a/Lombiq.JsonEditor.Test.UI/License.md
+++ b/Lombiq.JsonEditor.Test.UI/License.md
@@ -1,4 +1,4 @@
-Copyright © 2023, [Lombiq Technologies Ltd.](https://lombiq.com)
+Copyright © 2021, [Lombiq Technologies Ltd.](https://lombiq.com)
 
 All rights reserved.
 
diff --git a/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj b/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
index 076b630..be317bb 100644
--- a/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
+++ b/Lombiq.JsonEditor.Test.UI/Lombiq.JsonEditor.Tests.UI.csproj
@@ -7,7 +7,7 @@
   <PropertyGroup>
     <Title>Lombiq JSON Editor for Orchard Core - UI Test Extensions</Title>
     <Authors>Lombiq Technologies</Authors>
-    <Copyright>Copyright © 2023, Lombiq Technologies Ltd.</Copyright>
+    <Copyright>Copyright © 2021, Lombiq Technologies Ltd.</Copyright>
     <Description>Lombiq JSON Editor for Orchard Core - UI Test Extensions: Extensions to aid in UI testing Lombiq JSON Editor for Orchard Core.</Description>
     <PackageIcon>NuGetIcon.png</PackageIcon>
     <PackageTags>OrchardCore;Lombiq;AspNetCore;JSONEditor</PackageTags>
diff --git a/Lombiq.JsonEditor.sln b/Lombiq.JsonEditor.sln
deleted file mode 100644
index 4e458c4..0000000
--- a/Lombiq.JsonEditor.sln
+++ /dev/null
@@ -1,31 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.6.33815.320
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.JsonEditor", "Lombiq.JsonEditor\Lombiq.JsonEditor.csproj", "{3EA5BC81-97A5-4513-9CAC-4DB01B439682}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.JsonEditor.Tests.UI", "Lombiq.JsonEditor.Test.UI\Lombiq.JsonEditor.Tests.UI.csproj", "{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Release|Any CPU = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Release|Any CPU.Build.0 = Release|Any CPU
-		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Release|Any CPU.Build.0 = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-	GlobalSection(ExtensibilityGlobals) = postSolution
-		SolutionGuid = {67F3FCAA-D525-42C7-9743-B8D896F485BA}
-	EndGlobalSection
-EndGlobal
diff --git a/Lombiq.JsonEditor/.editorconfig b/Lombiq.JsonEditor/.editorconfig
deleted file mode 100644
index 1791262..0000000
--- a/Lombiq.JsonEditor/.editorconfig
+++ /dev/null
@@ -1,310 +0,0 @@
-# WARNING: Only edit this file in the Lombiq .NET Analyzers repository's "Lombiq.Analyzers" folder. A copy of this file
-# anywhere else will be overwritten.
-
-# Common .NET conventions, code formatting and naming convention rules. Check out possible configs here:
-# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
-# If you found some issue then try to fix it, which you can also do from VS Quick Actions:
-# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#automatically-configure-code-styles
-# Add new file types at the bottom. For new rules for C# files follow the structure that VS generates.
-
-# All files
-[*]
-
-# Basics
-charset = utf-8
-guidelines = 120 1px solid a0ffc000, 150 1px solid 80ff0000
-
-# Indentation and spacing
-indent_size = 4
-indent_style = space
-trim_trailing_whitespace = true
-
-
-# New line preferences
-end_of_line = crlf
-insert_final_newline = true
-dotnet_style_operator_placement_when_wrapping = beginning_of_line
-tab_width = 4
-dotnet_style_coalesce_expression = true:warning
-dotnet_style_null_propagation = true:warning
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
-dotnet_style_prefer_auto_properties = true:warning
-
-# C# files
-[*.cs]
-
-#### .NET Coding Conventions ####
-
-# Organize usings
-dotnet_separate_import_directive_groups = false
-dotnet_sort_system_directives_first = false
-
-# this. and Me. preferences
-dotnet_style_qualification_for_event = false:warning
-dotnet_style_qualification_for_field = false:warning
-dotnet_style_qualification_for_method = false:warning
-dotnet_style_qualification_for_property = false:warning
-
-# Language keywords vs BCL types preferences
-dotnet_style_predefined_type_for_locals_parameters_members = true:warning
-dotnet_style_predefined_type_for_member_access = true:warning
-
-# Parentheses preferences
-dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
-dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
-dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
-dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
-
-# Modifier preferences
-dotnet_style_require_accessibility_modifiers = always:warning
-
-# Expression-level preferences
-dotnet_style_coalesce_expression = true:warning
-dotnet_style_collection_initializer = true:warning
-dotnet_style_explicit_tuple_names = true:warning
-dotnet_style_null_propagation = true:warning
-dotnet_style_object_initializer = true:warning
-dotnet_style_prefer_auto_properties = true:warning
-dotnet_style_prefer_compound_assignment = true:warning
-dotnet_style_prefer_conditional_expression_over_assignment = true:warning
-dotnet_style_prefer_conditional_expression_over_return = false
-dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
-dotnet_style_prefer_inferred_tuple_names = true:warning
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
-dotnet_style_prefer_simplified_interpolation = true:warning
-
-# Field preferences
-dotnet_style_readonly_field = true:warning
-
-# Parameter preferences
-dotnet_code_quality_unused_parameters = non_pubic:suggestion
-
-#### C# Style Rules ####
-
-#IDE* rules are managed here.
-
-# Note that currently both IDE* rules and csharp_style_* rules are necessary, because only IDE rules will be enforced
-# during build, see: https://github.com/dotnet/roslyn/issues/44201.
-
-# Default severity for analyzer diagnostics for all categories (escalated to build warnings). See
-# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/categories for the list of categories.
-dotnet_analyzer_diagnostic.category-Design.severity = warning
-dotnet_analyzer_diagnostic.category-Documentation.severity = warning
-dotnet_analyzer_diagnostic.category-Globalization.severity = warning
-dotnet_analyzer_diagnostic.category-Interoperability.severity = warning
-dotnet_analyzer_diagnostic.category-Maintainability.severity = warning
-dotnet_analyzer_diagnostic.category-Naming.severity = warning
-dotnet_analyzer_diagnostic.category-Performance.severity = warning
-dotnet_analyzer_diagnostic.category-SingleFile.severity = warning
-dotnet_analyzer_diagnostic.category-Reliability.severity = warning
-dotnet_analyzer_diagnostic.category-Security.severity = warning
-dotnet_analyzer_diagnostic.category-Style.severity = warning
-dotnet_analyzer_diagnostic.category-Usage.severity = warning
-dotnet_analyzer_diagnostic.category-CodeQuality.severity = warning
-
-# IDE0011: Add braces to 'if' statement.
-# The "when-multiline:warning" config is not actually for cases when the if body is in another line so we have to turn
-# this off completely, see: https://github.com/dotnet/roslyn/issues/40912.
-dotnet_diagnostic.IDE0011.severity = none
-# IDE0050: Convert to tuple
-# Quite dangerous as we most frequently use anonymous types to interface with other APIs (like generating routes) and
-# those can fail on this silently in runtime while building correctly.
-dotnet_diagnostic.IDE0050.severity = none
-# IDE0052: Private member can be removed as the value assigned to it is never used.
-# We use S4487 for that.
-dotnet_diagnostic.IDE0052.severity = none
-# IDE0072: Populate switch
-# Also signals on switches with discards, by design: https://github.com/dotnet/roslyn/issues/48876.
-dotnet_diagnostic.IDE0072.severity = none
-# "Namespace 'Foo' does not match folder structure, expected 'Foo'"
-dotnet_diagnostic.IDE0130.severity = none
-
-# While these are in the Style category, they need to be explicitly set for some reason.
-# IDE0079 Remove unnecessary suppression
-dotnet_diagnostic.IDE0079.severity = warning
-dotnet_diagnostic.IDE0022.severity = warning
-
-# 'var' preferences
-dotnet_diagnostic.IDE0007.severity = warning
-dotnet_diagnostic.IDE0008.severity = none
-
-# We don't want to decorate every public member with XML comments, only public APIs - so turn CS1591 off.
-# CS1591 Missing XML Comment for publicly visible type or member
-dotnet_diagnostic.CS1591.severity = none
-
-#### C# Coding Conventions ####
-
-# 'var' preferences
-# These won't take effect during build due to this bug:
-# https://github.com/dotnet/roslyn/issues/44250
-csharp_style_var_elsewhere = true:warning
-# If this is not turned off then there will be messages for e.g. integers too, like in for loops.
-csharp_style_var_for_built_in_types = false:none
-csharp_style_var_when_type_is_apparent = true:warning
-
-# Expression-bodied members
-csharp_style_expression_bodied_accessors = true:warning
-csharp_style_expression_bodied_constructors = true:warning
-csharp_style_expression_bodied_indexers = true:warning
-csharp_style_expression_bodied_lambdas = true:warning
-csharp_style_expression_bodied_local_functions = true:warning
-csharp_style_expression_bodied_methods = true:warning
-csharp_style_expression_bodied_operators = true:warning
-csharp_style_expression_bodied_properties = true:warning
-
-# Pattern matching preferences
-csharp_style_pattern_matching_over_as_with_null_check = true:warning
-csharp_style_pattern_matching_over_is_with_cast_check = true:warning
-csharp_style_prefer_switch_expression = true:warning
-
-# Null-checking preferences
-csharp_style_conditional_delegate_call = true:warning
-
-# Modifier preferences
-csharp_prefer_static_local_function = true:warning
-csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
-
-# Code-block preferences
-csharp_prefer_simple_using_statement = true:warning
-# IDE0160: Convert to block-scoped namespace
-csharp_style_namespace_declarations = file_scoped:warning
-
-# Expression-level preferences
-csharp_prefer_simple_default_expression = true:warning
-csharp_style_deconstructed_variable_declaration = false:silent
-csharp_style_inlined_variable_declaration = true:warning
-csharp_style_pattern_local_over_anonymous_function = true:warning
-csharp_style_prefer_index_operator = true:warning
-csharp_style_prefer_range_operator = true:warning
-csharp_style_throw_expression = true:warning
-csharp_style_unused_value_assignment_preference = discard_variable:suggestion
-csharp_style_unused_value_expression_statement_preference = discard_variable:none
-
-# 'using' directive preferences
-csharp_using_directive_placement = outside_namespace:silent
-
-#### C# Formatting Rules ####
-
-# New line preferences
-csharp_new_line_before_catch = true
-csharp_new_line_before_else = true
-csharp_new_line_before_finally = true
-csharp_new_line_before_members_in_anonymous_types = true
-csharp_new_line_before_members_in_object_initializers = true
-csharp_new_line_before_open_brace = all
-csharp_new_line_between_query_expression_clauses = true
-
-# Indentation preferences
-csharp_indent_block_contents = true
-csharp_indent_braces = false
-csharp_indent_case_contents = true
-csharp_indent_case_contents_when_block = true
-csharp_indent_labels = no_change
-csharp_indent_switch_labels = true
-
-# Space preferences
-csharp_space_after_cast = false
-csharp_space_after_colon_in_inheritance_clause = true
-csharp_space_after_comma = true
-csharp_space_after_dot = false
-csharp_space_after_keywords_in_control_flow_statements = true
-csharp_space_after_semicolon_in_for_statement = true
-csharp_space_around_binary_operators = before_and_after
-csharp_space_around_declaration_statements = false
-csharp_space_before_colon_in_inheritance_clause = true
-csharp_space_before_comma = false
-csharp_space_before_dot = false
-csharp_space_before_open_square_brackets = false
-csharp_space_before_semicolon_in_for_statement = false
-csharp_space_between_empty_square_brackets = false
-csharp_space_between_method_call_empty_parameter_list_parentheses = false
-csharp_space_between_method_call_name_and_opening_parenthesis = false
-csharp_space_between_method_call_parameter_list_parentheses = false
-csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
-csharp_space_between_method_declaration_name_and_open_parenthesis = false
-csharp_space_between_method_declaration_parameter_list_parentheses = false
-csharp_space_between_parentheses = false
-csharp_space_between_square_brackets = false
-
-# Wrapping preferences
-csharp_preserve_single_line_blocks = true
-csharp_preserve_single_line_statements = true
-
-#### Naming styles ####
-
-# Naming rules
-
-dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
-dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
-dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
-
-dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
-dotnet_naming_rule.types_should_be_pascal_case.symbols = types
-dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
-
-dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
-dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
-dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
-
-# Symbol specifications
-
-dotnet_naming_symbols.interface.applicable_kinds = interface
-dotnet_naming_symbols.interface.applicable_accessibilities = *
-dotnet_naming_symbols.interface.required_modifiers =
-
-dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
-dotnet_naming_symbols.types.applicable_accessibilities = *
-dotnet_naming_symbols.types.required_modifiers =
-
-dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
-dotnet_naming_symbols.non_field_members.applicable_accessibilities = *
-dotnet_naming_symbols.non_field_members.required_modifiers =
-
-# Naming styles
-
-dotnet_naming_style.pascal_case.required_prefix =
-dotnet_naming_style.pascal_case.required_suffix =
-dotnet_naming_style.pascal_case.word_separator =
-dotnet_naming_style.pascal_case.capitalization = pascal_case
-
-dotnet_naming_style.begins_with_i.required_prefix = I
-dotnet_naming_style.begins_with_i.required_suffix =
-dotnet_naming_style.begins_with_i.word_separator =
-dotnet_naming_style.begins_with_i.capitalization = pascal_case
-
-# Disabling "catch a more specific exception type" suggestion which is overwhelmingly a false positive.
-dotnet_diagnostic.CA1031.severity = silent
-# Disabling "do not nest type" suggestion.
-dotnet_diagnostic.CA1034.severity = silent
-# Disabling "CA1810: Initialize reference type static fields inline" as RSPEC-3963 is used instead.
-dotnet_diagnostic.CA1810.severity = none
-
-# Don't apply "CA1822: Mark members as static" to the public API
-dotnet_code_quality.ca1822.api_surface = private, internal
-csharp_prefer_braces = true:silent
-
-# Various config files
-[*.{config,csproj,json,props,targets}]
-
-indent_size = 2
-
-# Markdown files
-[*.md]
-
-trim_trailing_whitespace = false
-
-# JavaScript files
-[*.js]
-
-# Placeholder, no unique rules for JS files at the moment.
-
-
-# SCSS files
-[*.scss]
-
-# Placeholder, no unique rules for SCSS files at the moment.
-
-# PowerShell files
-[*.ps1]
-
-# Placeholder, no unique rules for PS files at the moment.

From 0a62ad5e1f443dad05d9122c9979307424382cc1 Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Tue, 8 Aug 2023 15:55:53 +0200
Subject: [PATCH 12/16] Using CheckExistence

---
 .../TestCaseUITestContextExtensions.cs          | 17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)

diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
index 6c8c8ac..3ab6425 100644
--- a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
+++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
@@ -66,19 +66,12 @@ public static async Task TestJsonEditorBehaviorAsync(this UITestContext context)
         context.TestCodeStyleMode();
     }
 
-    private static void CheckValueInTreeMode(this UITestContext context, string arrayValue, bool missing = false)
+    private static void CheckValueInTreeMode(this UITestContext context, string arrayValue, bool exists = true)
     {
         var arrayValueByXPath =
             By.XPath($"//div[@class='jsoneditor-value jsoneditor-string' and contains(text(),'{arrayValue}')]");
 
-        if (!missing)
-        {
-            context.Exists(arrayValueByXPath);
-        }
-        else
-        {
-            context.Missing(arrayValueByXPath);
-        }
+        context.CheckExistence(arrayValueByXPath, exists);
     }
 
     private static Task ClickOnExpandAllAsync(this UITestContext context) =>
@@ -123,9 +116,9 @@ private static async Task TestTreeStyleModeAsync(this UITestContext context)
 
         context.Missing(FieldByXPath);
 
-        context.CheckValueInTreeMode(HelloValue, missing: true);
-        context.CheckValueInTreeMode(WorldValue, missing: true);
-        context.CheckValueInTreeMode(TestValue, missing: true);
+        context.CheckValueInTreeMode(HelloValue, exists: false);
+        context.CheckValueInTreeMode(WorldValue, exists: false);
+        context.CheckValueInTreeMode(TestValue, exists: false);
     }
 
     private static void TestCodeStyleMode(this UITestContext context)

From a78247905ce76563167ad5d1db6d42a648217ade Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Tue, 8 Aug 2023 16:19:56 +0200
Subject: [PATCH 13/16] Specifying a better selector for the button in the UI
 test

---
 .../Extensions/TestCaseUITestContextExtensions.cs            | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
index 3ab6425..9a3bad5 100644
--- a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
+++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs
@@ -39,7 +39,10 @@ public static async Task TestJsonEditorBehaviorAsync(this UITestContext context)
         await context.GoToContentItemEditorByIdAsync(SampleContentItemId);
 
         // Testing if input is saved.
-        context.GetAll(By.XPath($"//button[@class='jsoneditor-button jsoneditor-contextmenu-button']"))[1].Click();
+        await context.ClickReliablyOnAsync(
+            By.XPath($"//tr[contains(@class,'jsoneditor-expandable jsoneditor-collapsed')]" +
+                "/td/button[@class='jsoneditor-button jsoneditor-contextmenu-button']"));
+
         await context.ClickReliablyOnAsync(By.XPath($"//div[contains(text(),'Append')]"));
 
         context.Get(By.XPath($"//div[@class='jsoneditor-field jsoneditor-empty']")).FillInWith(TestField);

From 8e394194097706a2dec0dc50c121d1c8e13e4d78 Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Tue, 8 Aug 2023 18:42:42 +0200
Subject: [PATCH 14/16] Adding back sln file

---
 Lombiq.JsonEditor.sln | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 Lombiq.JsonEditor.sln

diff --git a/Lombiq.JsonEditor.sln b/Lombiq.JsonEditor.sln
new file mode 100644
index 0000000..4e458c4
--- /dev/null
+++ b/Lombiq.JsonEditor.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.6.33815.320
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.JsonEditor", "Lombiq.JsonEditor\Lombiq.JsonEditor.csproj", "{3EA5BC81-97A5-4513-9CAC-4DB01B439682}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.JsonEditor.Tests.UI", "Lombiq.JsonEditor.Test.UI\Lombiq.JsonEditor.Tests.UI.csproj", "{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3EA5BC81-97A5-4513-9CAC-4DB01B439682}.Release|Any CPU.Build.0 = Release|Any CPU
+		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{19F9AEDF-9D1A-44EC-BAEA-FF050CF7A684}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {67F3FCAA-D525-42C7-9743-B8D896F485BA}
+	EndGlobalSection
+EndGlobal

From 9c7b397bbf3506e869009abc9e6b76154bf0b505 Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Tue, 8 Aug 2023 18:43:29 +0200
Subject: [PATCH 15/16] Removing the other sln file

---
 Lombiq.JsonEditor/Lombiq.JsonEditor.sln | 31 -------------------------
 1 file changed, 31 deletions(-)
 delete mode 100644 Lombiq.JsonEditor/Lombiq.JsonEditor.sln

diff --git a/Lombiq.JsonEditor/Lombiq.JsonEditor.sln b/Lombiq.JsonEditor/Lombiq.JsonEditor.sln
deleted file mode 100644
index 98a8431..0000000
--- a/Lombiq.JsonEditor/Lombiq.JsonEditor.sln
+++ /dev/null
@@ -1,31 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.6.33815.320
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.JsonEditor", "Lombiq.JsonEditor.csproj", "{CBBDBDAF-53C0-48DC-9020-2F73C5C6F687}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.JsonEditor.Tests.UI", "..\Lombiq.JsonEditor.Test.UI\Lombiq.JsonEditor.Tests.UI.csproj", "{EE600BED-156B-4D25-8861-056D56AA1FD0}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Release|Any CPU = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{CBBDBDAF-53C0-48DC-9020-2F73C5C6F687}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{CBBDBDAF-53C0-48DC-9020-2F73C5C6F687}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{CBBDBDAF-53C0-48DC-9020-2F73C5C6F687}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{CBBDBDAF-53C0-48DC-9020-2F73C5C6F687}.Release|Any CPU.Build.0 = Release|Any CPU
-		{EE600BED-156B-4D25-8861-056D56AA1FD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{EE600BED-156B-4D25-8861-056D56AA1FD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{EE600BED-156B-4D25-8861-056D56AA1FD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{EE600BED-156B-4D25-8861-056D56AA1FD0}.Release|Any CPU.Build.0 = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-	GlobalSection(ExtensibilityGlobals) = postSolution
-		SolutionGuid = {67F3FCAA-D525-42C7-9743-B8D896F485BA}
-	EndGlobalSection
-EndGlobal

From 8346ac41316497d0bd5f893fa5feda6a9e65c1a8 Mon Sep 17 00:00:00 2001
From: Szabolcs Deme <szabolcs.deme@lombiq.com>
Date: Tue, 8 Aug 2023 20:37:37 +0200
Subject: [PATCH 16/16] Fixing css not loading

---
 Lombiq.JsonEditor/package.json | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/Lombiq.JsonEditor/package.json b/Lombiq.JsonEditor/package.json
index 660d280..2f040ed 100644
--- a/Lombiq.JsonEditor/package.json
+++ b/Lombiq.JsonEditor/package.json
@@ -15,6 +15,10 @@
         "sources": [ "node_modules/jsoneditor/dist" ],
         "target": "wwwroot/vendors/jsoneditor"
       }
-    ]
+    ],
+    "styles": {
+      "source": "Assets/Styles",
+      "target": "wwwroot/css"
+    }
   }
 }