From 57254e2fbf11eb7211dc29191797d7796de6c967 Mon Sep 17 00:00:00 2001 From: Kurtis Date: Mon, 8 Jun 2026 15:50:21 -0700 Subject: [PATCH 1/2] Add tests --- .../MRTKDevTemplate/Packages/manifest.json | 1 + org.mixedrealitytoolkit.theming/Tests.meta | 8 ++ .../Tests/Editor.meta | 8 ++ .../Tests/Editor/AssemblyInfo.cs | 12 ++ .../Tests/Editor/AssemblyInfo.cs.meta | 11 ++ .../Tests/Editor/BaseThemeBinderTests.cs | 125 ++++++++++++++++++ .../Tests/Editor/BaseThemeBinderTests.cs.meta | 11 ++ .../Editor/MRTK.Theming.EditorTests.asmdef | 31 +++++ .../MRTK.Theming.EditorTests.asmdef.meta | 7 + .../Tests/Editor/PackageValidationTest.cs | 32 +++++ .../Editor/PackageValidationTest.cs.meta | 11 ++ 11 files changed, 257 insertions(+) create mode 100644 org.mixedrealitytoolkit.theming/Tests.meta create mode 100644 org.mixedrealitytoolkit.theming/Tests/Editor.meta create mode 100644 org.mixedrealitytoolkit.theming/Tests/Editor/AssemblyInfo.cs create mode 100644 org.mixedrealitytoolkit.theming/Tests/Editor/AssemblyInfo.cs.meta create mode 100644 org.mixedrealitytoolkit.theming/Tests/Editor/BaseThemeBinderTests.cs create mode 100644 org.mixedrealitytoolkit.theming/Tests/Editor/BaseThemeBinderTests.cs.meta create mode 100644 org.mixedrealitytoolkit.theming/Tests/Editor/MRTK.Theming.EditorTests.asmdef create mode 100644 org.mixedrealitytoolkit.theming/Tests/Editor/MRTK.Theming.EditorTests.asmdef.meta create mode 100644 org.mixedrealitytoolkit.theming/Tests/Editor/PackageValidationTest.cs create mode 100644 org.mixedrealitytoolkit.theming/Tests/Editor/PackageValidationTest.cs.meta diff --git a/UnityProjects/MRTKDevTemplate/Packages/manifest.json b/UnityProjects/MRTKDevTemplate/Packages/manifest.json index 16cea28ea..fc096fc1a 100644 --- a/UnityProjects/MRTKDevTemplate/Packages/manifest.json +++ b/UnityProjects/MRTKDevTemplate/Packages/manifest.json @@ -82,6 +82,7 @@ "org.mixedrealitytoolkit.input", "org.mixedrealitytoolkit.spatialmanipulation", "org.mixedrealitytoolkit.standardassets", + "org.mixedrealitytoolkit.theming", "org.mixedrealitytoolkit.uxcomponents", "org.mixedrealitytoolkit.uxcomponents.noncanvas", "org.mixedrealitytoolkit.uxcore", diff --git a/org.mixedrealitytoolkit.theming/Tests.meta b/org.mixedrealitytoolkit.theming/Tests.meta new file mode 100644 index 000000000..0aa4b53d4 --- /dev/null +++ b/org.mixedrealitytoolkit.theming/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f15c71cd4449f464592b9317d8df9037 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/org.mixedrealitytoolkit.theming/Tests/Editor.meta b/org.mixedrealitytoolkit.theming/Tests/Editor.meta new file mode 100644 index 000000000..28c724e82 --- /dev/null +++ b/org.mixedrealitytoolkit.theming/Tests/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 05341ca71d86b4749837802d9272b087 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/org.mixedrealitytoolkit.theming/Tests/Editor/AssemblyInfo.cs b/org.mixedrealitytoolkit.theming/Tests/Editor/AssemblyInfo.cs new file mode 100644 index 000000000..a01411c1b --- /dev/null +++ b/org.mixedrealitytoolkit.theming/Tests/Editor/AssemblyInfo.cs @@ -0,0 +1,12 @@ +// Copyright (c) Mixed Reality Toolkit Contributors +// Licensed under the BSD 3-Clause + +using System.Reflection; + +[assembly: AssemblyProduct("Mixed Reality Toolkit UX Theming Editor Tests")] +[assembly: AssemblyCopyright("Copyright (c) Mixed Reality Toolkit Contributors")] + +// The AssemblyVersion attribute is checked-in and is recommended not to be changed often. +// https://docs.microsoft.com/troubleshoot/visualstudio/general/assembly-version-assembly-file-version +// AssemblyFileVersion and AssemblyInformationalVersion are added by pack-upm.ps1 to match the current MRTK build version. +[assembly: AssemblyVersion("1.0.0.0")] diff --git a/org.mixedrealitytoolkit.theming/Tests/Editor/AssemblyInfo.cs.meta b/org.mixedrealitytoolkit.theming/Tests/Editor/AssemblyInfo.cs.meta new file mode 100644 index 000000000..d5ad5babc --- /dev/null +++ b/org.mixedrealitytoolkit.theming/Tests/Editor/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b447f4a2c57e514ca8ccb9ee04e1d45 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/org.mixedrealitytoolkit.theming/Tests/Editor/BaseThemeBinderTests.cs b/org.mixedrealitytoolkit.theming/Tests/Editor/BaseThemeBinderTests.cs new file mode 100644 index 000000000..dad7846e6 --- /dev/null +++ b/org.mixedrealitytoolkit.theming/Tests/Editor/BaseThemeBinderTests.cs @@ -0,0 +1,125 @@ +// Copyright (c) Mixed Reality Toolkit Contributors +// Licensed under the BSD 3-Clause + +using NUnit.Framework; +using System; +using System.Linq; +using UnityEditor; + +namespace MixedReality.Toolkit.Theming.Tests.EditMode +{ + /// + /// Tests to evaluate the architectural integrity of Theme Binders. + /// + public class BaseThemeBinderTests + { + /// + /// Verifies that all non-abstract theme binders have fully resolved their generic arguments (T and K). + /// + [Test] + public void ConcreteBindersFullyResolveGenericArguments() + { + // Quickly grab every class in the project that implements IBinder + var binderTypes = TypeCache.GetTypesDerivedFrom(typeof(IBinder)) + .Where(t => !t.IsAbstract && !t.IsInterface); + + int testedCount = 0; + + foreach (Type type in binderTypes) + { + Assert.IsFalse(type.ContainsGenericParameters, + $"Binder type '{type.Name}' is not abstract, but contains open generic parameters. " + + "Concrete binders must fully resolve their BaseThemeBinder data types."); + + Type baseBinderType = type; + while (baseBinderType != null && (!baseBinderType.IsGenericType || baseBinderType.GetGenericTypeDefinition() != typeof(BaseThemeBinder<,>))) + { + baseBinderType = baseBinderType.BaseType; + } + + if (baseBinderType != null) + { + Type valueType = baseBinderType.GenericTypeArguments[0]; + Type targetType = baseBinderType.GenericTypeArguments[1]; + + Assert.IsNotNull(valueType, $"Value type (T) for '{type.Name}' could not be resolved."); + Assert.IsNotNull(targetType, $"Target type (K) for '{type.Name}' could not be resolved."); + + testedCount++; + } + } + + Assert.Greater(testedCount, 0, "No concrete BaseThemeBinder implementations were found to test."); + } + + /// + /// Verifies that all non-abstract theme item data classes have the [Serializable] attribute. + /// + [Test] + public void ConcreteThemeItemDataClassesAreSerializable() + { + var itemDataTypes = TypeCache.GetTypesDerivedFrom(typeof(BaseThemeItemData<>)) + .Where(t => !t.IsAbstract && !t.IsInterface); + + int testedCount = 0; + foreach (Type type in itemDataTypes) + { + Assert.IsTrue(type.IsSerializable, + $"Theme item data type '{type.Name}' must have the [Serializable] attribute to be saved in a Theme asset."); + testedCount++; + } + + Assert.Greater(testedCount, 0, "No concrete BaseThemeItemData implementations were found to test."); + } + + /// + /// Verifies that every generic data type targeted by a binder has a corresponding concrete ThemeItemData class to hold it. + /// + [Test] + public void BindersHaveCorrespondingThemeItemData() + { + var binderTypes = TypeCache.GetTypesDerivedFrom(typeof(IBinder)) + .Where(t => !t.IsAbstract && !t.IsInterface); + + var itemDataTypes = TypeCache.GetTypesDerivedFrom(typeof(BaseThemeItemData<>)) + .Where(t => !t.IsAbstract && !t.IsInterface).ToList(); + + int testedCount = 0; + foreach (Type binderType in binderTypes) + { + Type baseBinderType = binderType; + while (baseBinderType != null && (!baseBinderType.IsGenericType || baseBinderType.GetGenericTypeDefinition() != typeof(BaseThemeBinder<,>))) + { + baseBinderType = baseBinderType.BaseType; + } + + if (baseBinderType != null) + { + Type valueType = baseBinderType.GenericTypeArguments[0]; + + bool hasMatchingItemData = false; + foreach (Type itemDataType in itemDataTypes) + { + Type baseItemDataType = itemDataType; + while (baseItemDataType != null && (!baseItemDataType.IsGenericType || baseItemDataType.GetGenericTypeDefinition() != typeof(BaseThemeItemData<>))) + { + baseItemDataType = baseItemDataType.BaseType; + } + + if (baseItemDataType != null && baseItemDataType.GenericTypeArguments[0] == valueType) + { + hasMatchingItemData = true; + break; + } + } + + Assert.IsTrue(hasMatchingItemData, + $"Binder '{binderType.Name}' binds to '{valueType.Name}', but no concrete BaseThemeItemData<{valueType.Name}> class exists to serialize it in the Theme asset."); + testedCount++; + } + } + + Assert.Greater(testedCount, 0, "No concrete BaseThemeBinder implementations were found to test."); + } + } +} diff --git a/org.mixedrealitytoolkit.theming/Tests/Editor/BaseThemeBinderTests.cs.meta b/org.mixedrealitytoolkit.theming/Tests/Editor/BaseThemeBinderTests.cs.meta new file mode 100644 index 000000000..98239b300 --- /dev/null +++ b/org.mixedrealitytoolkit.theming/Tests/Editor/BaseThemeBinderTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 033e10d7e483c314386bb9b93fb16ed9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/org.mixedrealitytoolkit.theming/Tests/Editor/MRTK.Theming.EditorTests.asmdef b/org.mixedrealitytoolkit.theming/Tests/Editor/MRTK.Theming.EditorTests.asmdef new file mode 100644 index 000000000..0aaf086e0 --- /dev/null +++ b/org.mixedrealitytoolkit.theming/Tests/Editor/MRTK.Theming.EditorTests.asmdef @@ -0,0 +1,31 @@ +{ + "name": "MixedReality.Toolkit.Theming.Editor.Tests", + "rootNamespace": "MixedReality.Toolkit.Theming.Tests.EditMode", + "references": [ + "MixedReality.Toolkit.Theming", + "MixedReality.Toolkit.UXCore", + "UnityEngine.TestRunner", + "UnityEditor.TestRunner" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [ + { + "name": "com.unity.asset-store-validation", + "expression": "", + "define": "HAS_ASSET_STORE_VALIDATION" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/org.mixedrealitytoolkit.theming/Tests/Editor/MRTK.Theming.EditorTests.asmdef.meta b/org.mixedrealitytoolkit.theming/Tests/Editor/MRTK.Theming.EditorTests.asmdef.meta new file mode 100644 index 000000000..a0bda6f68 --- /dev/null +++ b/org.mixedrealitytoolkit.theming/Tests/Editor/MRTK.Theming.EditorTests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 07bb2e8be351e584b80703fb9df9603d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/org.mixedrealitytoolkit.theming/Tests/Editor/PackageValidationTest.cs b/org.mixedrealitytoolkit.theming/Tests/Editor/PackageValidationTest.cs new file mode 100644 index 000000000..4c9561e3f --- /dev/null +++ b/org.mixedrealitytoolkit.theming/Tests/Editor/PackageValidationTest.cs @@ -0,0 +1,32 @@ +// Copyright (c) Mixed Reality Toolkit Contributors +// Licensed under the BSD 3-Clause + +#if HAS_ASSET_STORE_VALIDATION + +using MixedReality.Toolkit.Core.Tests.EditMode; +using NUnit.Framework; +using System; + +namespace MixedReality.Toolkit.Theming.Tests.EditMode +{ + /// + /// This class is used to validate the package for the Mixed Reality Toolkit UX Theming package, verifying that all + /// requirements are met for publishing the package to the Unity Asset Store. + /// + internal class PackageValidationTest + { + /// + /// Test to validate the package for the Mixed Reality Toolkit UX Theming package, verifying that all + /// requirements are met for publishing the package to the Unity Asset Store. + /// + [Test] + public void PackageTest() + { + PackageValidatorResults results = PackageValidator.Validate("org.mixedrealitytoolkit.theming"); + Assert.AreEqual(0, results.FailedCount, $"Failed tests found.{Environment.NewLine}{results.ToString(PackageValidatorResults.MessageType.Failed)}"); + Assert.IsTrue(0 < results.SucceededCount, "No tests succeeded"); + } + } +} + +#endif // HAS_ASSET_STORE_VALIDATION diff --git a/org.mixedrealitytoolkit.theming/Tests/Editor/PackageValidationTest.cs.meta b/org.mixedrealitytoolkit.theming/Tests/Editor/PackageValidationTest.cs.meta new file mode 100644 index 000000000..4a16aba46 --- /dev/null +++ b/org.mixedrealitytoolkit.theming/Tests/Editor/PackageValidationTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 78b18fd01cb9ed64a97e55d09ba534e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From adcd539aa1052a99fd1dc13b1b8fecf622fc462c Mon Sep 17 00:00:00 2001 From: Kurtis Date: Thu, 11 Jun 2026 12:06:42 -0700 Subject: [PATCH 2/2] Update CHANGELOG.md --- org.mixedrealitytoolkit.theming/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/org.mixedrealitytoolkit.theming/CHANGELOG.md b/org.mixedrealitytoolkit.theming/CHANGELOG.md index 52880eb4c..febb779dc 100644 --- a/org.mixedrealitytoolkit.theming/CHANGELOG.md +++ b/org.mixedrealitytoolkit.theming/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). * Added serializable theme item data types for booleans. [PR #1130](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1130) * Added built-in binders for skybox materials and behaviour enabled states. [PR #1130](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1130) +* Added edit mode tests for validating custom theming types and their dependencies. [PR #1133](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1133) ## [1.0.0-pre.1] - 2026-05-20