com.wallstop-studios.unity-helpers 2.0.0-rc81.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +1 -1
- package/.gitattributes +1 -1
- package/.githooks/pre-commit +31 -5
- package/.githooks/pre-push +50 -0
- package/.github/dependabot.yml +24 -2
- package/.github/scripts/check-markdown-links.ps1 +77 -0
- package/.github/scripts/check_markdown_links.py +89 -0
- package/.github/scripts/check_markdown_url_encoding.py +74 -0
- package/.github/scripts/validate_markdown_links.py +194 -0
- package/.github/workflows/csharpier-autofix.yml +152 -0
- package/.github/workflows/format-on-demand.yml +305 -0
- package/.github/workflows/lint-doc-links.yml +8 -5
- package/.github/workflows/markdown-json.yml +6 -2
- package/.github/workflows/prettier-autofix.yml +195 -0
- package/.github/workflows/update-dotnet-tools.yml +80 -0
- package/.github/workflows/yaml-format-lint.yml +41 -0
- package/.lychee.toml +4 -4
- package/.markdownlint.jsonc +21 -0
- package/.pre-commit-config.yaml +11 -3
- package/.yamllint.yaml +31 -0
- package/AGENTS.md +5 -1
- package/CHANGELOG.md +11 -0
- package/CONTRIBUTING.md +49 -0
- package/CONTRIBUTING.md.meta +7 -0
- package/EDITOR_TOOLS_GUIDE.md +4 -0
- package/Editor/AnimationEventEditor.cs +337 -160
- package/Editor/Core/Helper/AnimationEventHelpers.cs +178 -152
- package/Editor/CustomEditors/PersistentDirectoryGUI.cs +20 -11
- package/Editor/CustomEditors/TexturePlatformOverrideEntryDrawer.cs +11 -2
- package/Editor/FitTextureSizeWindow.cs +43 -19
- package/Editor/PersistentDirectorySettings.cs +64 -12
- package/Editor/PrefabChecker.cs +72 -5
- package/Editor/Sprites/AnimationCopier.cs +132 -56
- package/Editor/Sprites/AnimationCreator.cs +63 -22
- package/Editor/Sprites/AnimationViewerWindow.cs +42 -6
- package/Editor/Sprites/TexturePlatformNameHelper.cs +50 -39
- package/Editor/Sprites/TextureResizerWizard.cs +23 -1
- package/Editor/Sprites/TextureSettingsApplierWindow.cs +148 -85
- package/Editor/Tools/ImageBlurTool.cs +81 -10
- package/Editor/Utils/EditorUi.cs +1 -1
- package/Editor/Utils/ScriptableObjectSingletonCreator.cs +1 -1
- package/GETTING_STARTED.md +40 -56
- package/RANDOM_PERFORMANCE.md +12 -12
- package/README.md +395 -2407
- package/RELATIONAL_COMPONENTS.md +92 -83
- package/Runtime/AssemblyInfo.cs +2 -0
- package/Runtime/Core/Attributes/NotNullAttribute.cs +1 -3
- package/Runtime/Core/Attributes/RelationalComponentAssigner.cs +50 -5
- package/Runtime/Core/DataStructure/CyclicBuffer.cs +0 -1
- package/Runtime/Core/Extension/RandomExtensions.cs +68 -0
- package/Runtime/Core/Extension/WallstopStudiosLogger.cs +16 -0
- package/Runtime/Core/Helper/Partials/ObjectHelpers.cs +4 -1
- package/Runtime/Core/Helper/ReflectionHelpers.cs +21 -10
- package/Runtime/Core/Helper/SpriteHelpers.cs +3 -1
- package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +45 -1
- package/Runtime/Core/Serialization/JsonConverters/GameObjectConverter.cs +13 -5
- package/Runtime/Core/Serialization/JsonConverters/ResolutionConverter.cs +1 -1
- package/Runtime/Core/Serialization/JsonConverters/TypeConverter.cs +1 -1
- package/Runtime/Core/Serialization/Serializer.cs +101 -0
- package/Runtime/Integrations/VContainer/AssemblyInfo.cs +9 -0
- package/Runtime/Integrations/VContainer/AssemblyInfo.cs.meta +3 -0
- package/Runtime/Integrations/VContainer/ObjectResolverRelationalExtensions.cs +96 -0
- package/Runtime/Integrations/VContainer/RelationalComponentEntryPoint.cs +90 -10
- package/Runtime/Integrations/VContainer/RelationalComponentsBuilderExtensions.cs +13 -1
- package/Runtime/Integrations/VContainer/RelationalObjectPools.cs +114 -0
- package/Runtime/Integrations/VContainer/RelationalObjectPools.cs.meta +11 -0
- package/Runtime/Integrations/VContainer/RelationalSceneAssignmentOptions.cs +16 -4
- package/Runtime/Integrations/VContainer/RelationalSceneLoadListener.cs +241 -0
- package/Runtime/Integrations/VContainer/RelationalSceneLoadListener.cs.meta +11 -0
- package/Runtime/Integrations/Zenject/AssemblyInfo.cs +9 -0
- package/Runtime/Integrations/Zenject/AssemblyInfo.cs.meta +3 -0
- package/Runtime/Integrations/Zenject/DiContainerRelationalExtensions.cs +69 -2
- package/Runtime/Integrations/Zenject/RelationalComponentSceneInitializer.cs +89 -12
- package/Runtime/Integrations/Zenject/RelationalComponentsInstaller.cs +23 -1
- package/Runtime/Integrations/Zenject/RelationalMemoryPools.cs +44 -0
- package/Runtime/Integrations/Zenject/RelationalMemoryPools.cs.meta +11 -0
- package/Runtime/Integrations/Zenject/RelationalSceneAssignmentOptions.cs +16 -10
- package/Runtime/Integrations/Zenject/RelationalSceneLoadListener.cs +243 -0
- package/Runtime/Integrations/Zenject/RelationalSceneLoadListener.cs.meta +11 -0
- package/Runtime/Tags/AttributeMetadataCache.cs +1 -4
- package/Runtime/Utils/Buffers.cs +4 -4
- package/Runtime/Utils/ScriptableObjectSingleton.cs +0 -1
- package/Runtime/Utils/SetTextureImportData.cs +3 -1
- package/Runtime/Utils/TextureScale.cs +10 -2
- package/Runtime/Visuals/UGUI/EnhancedImage.cs +6 -0
- package/Runtime/Visuals/UIToolkit/LayeredImage.cs +4 -1
- package/SERIALIZATION.md +15 -0
- package/SPATIAL_TREE_2D_PERFORMANCE.md +85 -82
- package/SPATIAL_TREE_3D_PERFORMANCE.md +94 -91
- package/Samples~/DI - VContainer/README.md +232 -51
- package/Samples~/DI - VContainer/Scripts/GameLifetimeScope.cs +22 -4
- package/Samples~/DI - VContainer/Scripts/RelationalConsumer.cs +5 -2
- package/Samples~/DI - VContainer/Scripts/Spawner.cs +113 -4
- package/Samples~/DI - Zenject/README.md +217 -53
- package/Samples~/DI - Zenject/Scripts/RelationalConsumer.cs +3 -0
- package/Samples~/DI - Zenject/Scripts/RelationalConsumerPool.cs +37 -0
- package/Samples~/DI - Zenject/Scripts/RelationalConsumerPool.cs.meta +12 -0
- package/Samples~/DI - Zenject/Scripts/SpawnerZenject.cs +74 -3
- package/Samples~/Random - PRNG/README.md +2 -1
- package/Samples~/Relational Components - Basic/README.md +3 -1
- package/Samples~/Serialization - JSON/README.md +2 -1
- package/Samples~/Spatial Structures - 2D and 3D/README.md +2 -1
- package/Samples~/UGUI - EnhancedImage/README.md +2 -1
- package/Samples~/UI Toolkit - MultiFile Selector (Editor)/README.md +2 -1
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/Tests/Editor/Attributes/AnimationEventHelpersTests.cs +16 -0
- package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +3 -3
- package/Tests/Editor/Integrations/VContainer/VContainerRelationalEntryPointTests.cs +6 -2
- package/Tests/Editor/Integrations/VContainer/VContainerRelationalHelpersTests.cs +170 -0
- package/Tests/Editor/Integrations/VContainer/VContainerRelationalHelpersTests.cs.meta +11 -0
- package/Tests/Editor/Integrations/VContainer/WallstopStudios.UnityHelpers.Tests.Editor.VContainer.asmdef +2 -1
- package/Tests/Editor/Integrations/Zenject/WallstopStudios.UnityHelpers.Tests.Editor.Zenject.asmdef +3 -2
- package/Tests/Editor/Integrations/Zenject/ZenjectRelationalHelpersTests.cs +131 -0
- package/Tests/Editor/Integrations/Zenject/ZenjectRelationalHelpersTests.cs.meta +11 -0
- package/Tests/Editor/Integrations/Zenject/ZenjectRelationalInitializerTests.cs +6 -2
- package/Tests/Editor/PersistentDirectorySettingsTests.cs +59 -0
- package/Tests/Editor/PersistentDirectorySettingsTests.cs.meta +11 -0
- package/Tests/Editor/PrefabCheckerReportTests.cs +32 -0
- package/Tests/Editor/PrefabCheckerReportTests.cs.meta +11 -0
- package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs +64 -0
- package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs.meta +11 -0
- package/Tests/Editor/Sprites/AnimationCopierWindowTests.cs +1 -1
- package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs +38 -0
- package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs.meta +11 -0
- package/Tests/Editor/Sprites/ScriptableSpriteAtlasEditorTests.cs +1 -1
- package/Tests/Editor/Sprites/SpriteCropperAdditionalTests.cs +12 -12
- package/Tests/Editor/Sprites/SpriteCropperTests.cs +9 -9
- package/Tests/Editor/Sprites/SpritePivotAdjusterTests.cs +3 -3
- package/Tests/Editor/Sprites/TexturePlatformNameHelperTests.cs +18 -0
- package/Tests/Editor/Sprites/TextureResizerWizardTests.cs +5 -5
- package/Tests/Editor/Sprites/TextureSettingsApplierAPITests.cs +3 -3
- package/Tests/Editor/Sprites/TextureSettingsApplierWizardAdditionalTests.cs +4 -4
- package/Tests/Editor/Sprites/TextureSettingsApplierWizardTests.cs +4 -4
- package/Tests/Editor/Tools/ImageBlurToolTests.cs +22 -110
- package/Tests/Editor/Utils/CommonTestBase.cs +43 -1
- package/Tests/Editor/Utils/ScriptableObjectSingletonCreatorTests.cs +5 -5
- package/Tests/Editor/Windows/FitTextureSizeWindowTests.cs +66 -74
- package/Tests/Runtime/Attributes/RelationalComponentInitializerTests.cs +4 -15
- package/Tests/Runtime/DataStructures/SpatialTree3DBoundsConsistencyTests.cs +29 -29
- package/Tests/Runtime/Integrations/VContainer/RelationalComponentsVContainerTests.cs +259 -218
- package/Tests/Runtime/Integrations/VContainer/RelationalObjectPoolsVContainerTests.cs +86 -0
- package/Tests/Runtime/Integrations/VContainer/RelationalObjectPoolsVContainerTests.cs.meta +11 -0
- package/Tests/Runtime/Integrations/Zenject/RelationalComponentsZenjectTests.cs +255 -227
- package/Tests/Runtime/Performance/SpatialTree2DPerformanceTests.cs +5 -0
- package/Tests/Runtime/Performance/SpatialTree3DPerformanceTests.cs +3 -0
- package/Tests/Runtime/Serialization/JsonConverterAdditionalTests.cs +30 -0
- package/Tests/Runtime/Serialization/JsonConverterAdditionalTests.cs.meta +11 -0
- package/Tests/Runtime/Serialization/JsonConverterTests.cs +8 -12
- package/Tests/Runtime/Serialization/JsonSerializationTest.cs +16 -5
- package/Tests/Runtime/Serialization/SerializerAdditionalTests.cs +12 -0
- package/Tests/Runtime/Serialization/SerializerFileIoTests.cs +105 -0
- package/Tests/Runtime/Serialization/SerializerFileIoTests.cs.meta +11 -0
- package/Tests/Runtime/Serialization/UnityEngineObjectJsonTests.cs +247 -0
- package/Tests/Runtime/Serialization/UnityEngineObjectJsonTests.cs.meta +11 -0
- package/Tests/Runtime/TestUtils/CommonTestBase.cs +88 -0
- package/Tests/Runtime/Utils/CoroutineHandlerTests.cs +1 -1
- package/Tests/Runtime/Utils/LZMAComprehensiveTests.cs +1 -1
- package/Tests/Runtime/Utils/LZMATests.cs +1 -1
- package/Tests/Runtime/Utils/MatchColliderToSpriteTests.cs +1 -1
- package/Tests/Runtime/Visuals/EnhancedImageTests.cs +25 -56
- package/Tests/Runtime/Visuals/VisualsTestHelpers.cs +1 -8
- package/package-lock.json.meta +7 -0
- package/package.json +8 -4
- package/scripts/check-eol.ps1 +4 -5
- package/scripts/lint-tests.ps1 +156 -0
- package/scripts/lint-tests.ps1.meta +7 -0
- package/scripts/normalize-eol.ps1 +6 -9
- package/.github/workflows/csharpier.yml +0 -135
|
@@ -3,10 +3,11 @@ UGUI – EnhancedImage
|
|
|
3
3
|
Shows programmatic setup and basic usage of `EnhancedImage` with HDR tinting.
|
|
4
4
|
|
|
5
5
|
How to use
|
|
6
|
+
|
|
6
7
|
- Add `EnhancedImageDemo` to an empty scene and press Play.
|
|
7
8
|
- Optionally assign a `materialTemplate` in the inspector to use a shader that exposes `_Color` and optional `_ShapeMask`.
|
|
8
9
|
|
|
9
10
|
What it shows
|
|
11
|
+
|
|
10
12
|
- Creating a `Canvas` and an `EnhancedImage` at runtime.
|
|
11
13
|
- Setting an HDR tint via `HdrColor` (values > 1).
|
|
12
|
-
|
|
@@ -3,10 +3,11 @@ UI Toolkit – MultiFile Selector (Editor)
|
|
|
3
3
|
A minimal EditorWindow showcasing `MultiFileSelectorElement` for fast in-Editor multi-file selection with breadcrumbs, filtering and virtualization.
|
|
4
4
|
|
|
5
5
|
How to use
|
|
6
|
+
|
|
6
7
|
- Import the sample, then open: `Window > Unity Helpers > MultiFile Selector Sample`.
|
|
7
8
|
- Pick extensions and select multiple files; results are logged to the Console.
|
|
8
9
|
|
|
9
10
|
What it shows
|
|
11
|
+
|
|
10
12
|
- Creating and adding `MultiFileSelectorElement` to an EditorWindow.
|
|
11
13
|
- Hooks for `OnFilesSelected` and `OnCancelled`.
|
|
12
|
-
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -27,7 +27,7 @@ Full License Texts
|
|
|
27
27
|
|
|
28
28
|
Apache License
|
|
29
29
|
Version 2.0, January 2004
|
|
30
|
-
[
|
|
30
|
+
[https://www.apache.org/licenses/](https://www.apache.org/licenses/)
|
|
31
31
|
|
|
32
32
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
33
33
|
|
|
@@ -93,6 +93,22 @@ namespace WallstopStudios.UnityHelpers.Tests.Editor.Attributes
|
|
|
93
93
|
|
|
94
94
|
CollectionAssert.AreEquivalent(expected, methods.Select(method => method.Name));
|
|
95
95
|
}
|
|
96
|
+
|
|
97
|
+
[Test]
|
|
98
|
+
public void GetPossibleAnimatorEventsAreSortedByName()
|
|
99
|
+
{
|
|
100
|
+
List<MethodInfo> methods = AnimationEventHelpers.GetPossibleAnimatorEventsForType(
|
|
101
|
+
typeof(AnimationEventSignatureHost)
|
|
102
|
+
);
|
|
103
|
+
// Ensure ascending ordinal sort by method name
|
|
104
|
+
for (int i = 1; i < methods.Count; i++)
|
|
105
|
+
{
|
|
106
|
+
Assert.LessOrEqual(
|
|
107
|
+
string.Compare(methods[i - 1].Name, methods[i].Name, StringComparison.Ordinal),
|
|
108
|
+
0
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
96
112
|
}
|
|
97
113
|
|
|
98
114
|
internal enum AnimationEventSignal
|
|
@@ -98,13 +98,13 @@ namespace WallstopStudios.UnityHelpers.Tests.Core.Attributes
|
|
|
98
98
|
GameObject go2 = NewGameObject("NonRelational");
|
|
99
99
|
NonRelational non = go2.AddComponent<NonRelational>();
|
|
100
100
|
|
|
101
|
-
Assert.
|
|
101
|
+
Assert.IsTrue(consumer.SR == null, "Precondition: relational field should start null");
|
|
102
102
|
|
|
103
103
|
List<Component> items = new List<Component> { consumer, null, non.transform };
|
|
104
104
|
assigner.Assign(items);
|
|
105
105
|
|
|
106
|
-
Assert.
|
|
107
|
-
consumer.SR,
|
|
106
|
+
Assert.IsTrue(
|
|
107
|
+
consumer.SR != null,
|
|
108
108
|
"Relational field should be assigned by Assign(IEnumerable<Component>)"
|
|
109
109
|
);
|
|
110
110
|
|
|
@@ -55,6 +55,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Integrations.VContainer
|
|
|
55
55
|
new[] { relationalMetadata }
|
|
56
56
|
);
|
|
57
57
|
cache.ForceRebuildForTests();
|
|
58
|
+
yield return null;
|
|
58
59
|
#endif
|
|
59
60
|
|
|
60
61
|
RelationalComponentAssigner assigner = new RelationalComponentAssigner(cache);
|
|
@@ -69,8 +70,11 @@ namespace WallstopStudios.UnityHelpers.Tests.Integrations.VContainer
|
|
|
69
70
|
// Allow assignment to complete
|
|
70
71
|
yield return null;
|
|
71
72
|
|
|
72
|
-
Assert.
|
|
73
|
-
Assert.
|
|
73
|
+
Assert.IsTrue(consumer != null);
|
|
74
|
+
Assert.IsTrue(
|
|
75
|
+
consumer.SR != null,
|
|
76
|
+
"Relational field should be assigned by entry point"
|
|
77
|
+
);
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#if VCONTAINER_PRESENT
|
|
2
|
+
namespace WallstopStudios.UnityHelpers.Tests.Integrations.VContainer
|
|
3
|
+
{
|
|
4
|
+
using System.Collections;
|
|
5
|
+
using global::VContainer;
|
|
6
|
+
using NUnit.Framework;
|
|
7
|
+
using UnityEngine;
|
|
8
|
+
using UnityEngine.SceneManagement;
|
|
9
|
+
using UnityEngine.TestTools;
|
|
10
|
+
using WallstopStudios.UnityHelpers.Core.Attributes;
|
|
11
|
+
using WallstopStudios.UnityHelpers.Integrations.VContainer;
|
|
12
|
+
using WallstopStudios.UnityHelpers.Tags;
|
|
13
|
+
using WallstopStudios.UnityHelpers.Tests.Editor.Utils;
|
|
14
|
+
|
|
15
|
+
public sealed class VContainerRelationalHelpersTests : CommonTestBase
|
|
16
|
+
{
|
|
17
|
+
private sealed class TestComponent : MonoBehaviour
|
|
18
|
+
{
|
|
19
|
+
[ParentComponent(OnlyAncestors = true)]
|
|
20
|
+
public Rigidbody parentBody;
|
|
21
|
+
|
|
22
|
+
[ChildComponent(OnlyDescendants = true)]
|
|
23
|
+
public CapsuleCollider childCollider;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
[Test]
|
|
27
|
+
public void InjectWithRelationsAssignsFields()
|
|
28
|
+
{
|
|
29
|
+
ContainerBuilder builder = new ContainerBuilder();
|
|
30
|
+
IObjectResolver resolver = builder.Build();
|
|
31
|
+
|
|
32
|
+
// Build hierarchy: Parent(Rigidbody) -> Middle(TestComponent) -> Child(CapsuleCollider)
|
|
33
|
+
GameObject parent = NewGameObject("Parent");
|
|
34
|
+
parent.AddComponent<Rigidbody>();
|
|
35
|
+
GameObject middle = NewGameObject("Middle");
|
|
36
|
+
middle.transform.SetParent(parent.transform);
|
|
37
|
+
TestComponent comp = middle.AddComponent<TestComponent>();
|
|
38
|
+
GameObject child = NewGameObject("Child");
|
|
39
|
+
child.transform.SetParent(middle.transform);
|
|
40
|
+
child.AddComponent<CapsuleCollider>();
|
|
41
|
+
|
|
42
|
+
TestComponent result = resolver.InjectWithRelations(comp);
|
|
43
|
+
|
|
44
|
+
Assert.That(result, Is.SameAs(comp));
|
|
45
|
+
Assert.IsTrue(comp.parentBody != null);
|
|
46
|
+
Assert.IsTrue(comp.childCollider != null);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[Test]
|
|
50
|
+
public void InstantiateComponentWithRelationsAssignsFields()
|
|
51
|
+
{
|
|
52
|
+
ContainerBuilder builder = new ContainerBuilder();
|
|
53
|
+
IObjectResolver resolver = builder.Build();
|
|
54
|
+
|
|
55
|
+
// Build prefab hierarchy: Root(Rigidbody) -> Middle(TestComponent) -> Child(CapsuleCollider)
|
|
56
|
+
GameObject prefabRoot = NewGameObject("PrefabRoot");
|
|
57
|
+
prefabRoot.AddComponent<Rigidbody>();
|
|
58
|
+
GameObject prefabMiddle = NewGameObject("PrefabMiddle");
|
|
59
|
+
prefabMiddle.transform.SetParent(prefabRoot.transform);
|
|
60
|
+
TestComponent prefabComp = prefabMiddle.AddComponent<TestComponent>();
|
|
61
|
+
GameObject prefabChild = NewGameObject("PrefabChild");
|
|
62
|
+
prefabChild.transform.SetParent(prefabMiddle.transform);
|
|
63
|
+
prefabChild.AddComponent<CapsuleCollider>();
|
|
64
|
+
|
|
65
|
+
// Instantiate the full hierarchy and hydrate via GO helper
|
|
66
|
+
GameObject instanceRoot = resolver.InstantiateGameObjectWithRelations(prefabRoot);
|
|
67
|
+
TestComponent instance = instanceRoot.GetComponentInChildren<TestComponent>(true);
|
|
68
|
+
|
|
69
|
+
Assert.IsTrue(instance != null);
|
|
70
|
+
Assert.IsTrue(instance.parentBody != null);
|
|
71
|
+
Assert.IsTrue(instance.childCollider != null);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
[Test]
|
|
75
|
+
public void InjectGameObjectWithRelationsAssignsHierarchy()
|
|
76
|
+
{
|
|
77
|
+
ContainerBuilder builder = new ContainerBuilder();
|
|
78
|
+
IObjectResolver resolver = builder.Build();
|
|
79
|
+
|
|
80
|
+
GameObject root = NewGameObject("Root");
|
|
81
|
+
root.AddComponent<Rigidbody>();
|
|
82
|
+
GameObject middle = NewGameObject("Middle");
|
|
83
|
+
middle.transform.SetParent(root.transform);
|
|
84
|
+
TestComponent comp = middle.AddComponent<TestComponent>();
|
|
85
|
+
GameObject child = NewGameObject("Child");
|
|
86
|
+
child.transform.SetParent(middle.transform);
|
|
87
|
+
child.AddComponent<CapsuleCollider>();
|
|
88
|
+
|
|
89
|
+
resolver.InjectGameObjectWithRelations(root);
|
|
90
|
+
|
|
91
|
+
Assert.IsTrue(comp.parentBody != null);
|
|
92
|
+
Assert.IsTrue(comp.childCollider != null);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
[Test]
|
|
96
|
+
public void InstantiateGameObjectWithRelationsAssignsHierarchy()
|
|
97
|
+
{
|
|
98
|
+
ContainerBuilder builder = new ContainerBuilder();
|
|
99
|
+
IObjectResolver resolver = builder.Build();
|
|
100
|
+
|
|
101
|
+
GameObject prefab = NewGameObject("PrefabRoot");
|
|
102
|
+
prefab.AddComponent<Rigidbody>();
|
|
103
|
+
GameObject mid = NewGameObject("PrefabMiddle");
|
|
104
|
+
mid.transform.SetParent(prefab.transform);
|
|
105
|
+
TestComponent prefabComp = mid.AddComponent<TestComponent>();
|
|
106
|
+
GameObject child = NewGameObject("PrefabChild");
|
|
107
|
+
child.transform.SetParent(mid.transform);
|
|
108
|
+
child.AddComponent<CapsuleCollider>();
|
|
109
|
+
|
|
110
|
+
GameObject instance = resolver.InstantiateGameObjectWithRelations(prefab);
|
|
111
|
+
TestComponent comp = instance.GetComponentInChildren<TestComponent>(true);
|
|
112
|
+
|
|
113
|
+
Assert.IsTrue(comp != null);
|
|
114
|
+
Assert.IsTrue(comp.parentBody != null);
|
|
115
|
+
Assert.IsTrue(comp.childCollider != null);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
[Test]
|
|
119
|
+
public void AssignerRecognizesBaseTypeMetadataForDerivedComponents()
|
|
120
|
+
{
|
|
121
|
+
AttributeMetadataCache cache = CreateScriptableObject<AttributeMetadataCache>();
|
|
122
|
+
|
|
123
|
+
AttributeMetadataCache.RelationalFieldMetadata[] fields =
|
|
124
|
+
{
|
|
125
|
+
new AttributeMetadataCache.RelationalFieldMetadata(
|
|
126
|
+
nameof(BaseWithSibling._spriteRenderer),
|
|
127
|
+
AttributeMetadataCache.RelationalAttributeKind.Sibling,
|
|
128
|
+
AttributeMetadataCache.FieldKind.Single,
|
|
129
|
+
typeof(SpriteRenderer).AssemblyQualifiedName,
|
|
130
|
+
false
|
|
131
|
+
),
|
|
132
|
+
};
|
|
133
|
+
AttributeMetadataCache.RelationalTypeMetadata[] relational =
|
|
134
|
+
{
|
|
135
|
+
new AttributeMetadataCache.RelationalTypeMetadata(
|
|
136
|
+
typeof(BaseWithSibling).AssemblyQualifiedName,
|
|
137
|
+
fields
|
|
138
|
+
),
|
|
139
|
+
};
|
|
140
|
+
cache._relationalTypeMetadata = relational;
|
|
141
|
+
cache.ForceRebuildForTests();
|
|
142
|
+
|
|
143
|
+
ContainerBuilder builder = new ContainerBuilder();
|
|
144
|
+
builder.RegisterInstance(cache).AsSelf();
|
|
145
|
+
builder
|
|
146
|
+
.RegisterInstance(new RelationalComponentAssigner(cache))
|
|
147
|
+
.As<IRelationalComponentAssigner>();
|
|
148
|
+
IObjectResolver resolver = builder.Build();
|
|
149
|
+
|
|
150
|
+
GameObject go = NewGameObject("Root");
|
|
151
|
+
go.AddComponent<SpriteRenderer>();
|
|
152
|
+
DerivedWithSibling comp = go.AddComponent<DerivedWithSibling>();
|
|
153
|
+
|
|
154
|
+
resolver.AssignRelationalComponents(comp);
|
|
155
|
+
|
|
156
|
+
Assert.IsTrue(comp.SR != null);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private class BaseWithSibling : MonoBehaviour
|
|
160
|
+
{
|
|
161
|
+
[SiblingComponent]
|
|
162
|
+
protected internal SpriteRenderer _spriteRenderer;
|
|
163
|
+
|
|
164
|
+
public SpriteRenderer SR => _spriteRenderer;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private sealed class DerivedWithSibling : BaseWithSibling { }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
#endif
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"UnityEngine.TestRunner",
|
|
7
7
|
"WallstopStudios.UnityHelpers",
|
|
8
8
|
"WallstopStudios.UnityHelpers.Integration.VContainer",
|
|
9
|
-
"WallstopStudios.UnityHelpers.Tests.Editor"
|
|
9
|
+
"WallstopStudios.UnityHelpers.Tests.Editor",
|
|
10
|
+
"VContainer"
|
|
10
11
|
],
|
|
11
12
|
"includePlatforms": ["Editor"],
|
|
12
13
|
"excludePlatforms": [],
|
package/Tests/Editor/Integrations/Zenject/WallstopStudios.UnityHelpers.Tests.Editor.Zenject.asmdef
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
"UnityEngine.TestRunner",
|
|
7
7
|
"WallstopStudios.UnityHelpers",
|
|
8
8
|
"WallstopStudios.UnityHelpers.Integration.Zenject",
|
|
9
|
-
"WallstopStudios.UnityHelpers.Tests.Editor"
|
|
9
|
+
"WallstopStudios.UnityHelpers.Tests.Editor",
|
|
10
|
+
"Zenject"
|
|
10
11
|
],
|
|
11
12
|
"includePlatforms": ["Editor"],
|
|
12
13
|
"excludePlatforms": [],
|
|
13
14
|
"allowUnsafeCode": false,
|
|
14
15
|
"overrideReferences": true,
|
|
15
|
-
"precompiledReferences": ["nunit.framework.dll"],
|
|
16
|
+
"precompiledReferences": ["nunit.framework.dll", "Zenject-usage.dll"],
|
|
16
17
|
"autoReferenced": false,
|
|
17
18
|
"defineConstraints": ["UNITY_INCLUDE_TESTS", "ZENJECT_PRESENT"],
|
|
18
19
|
"versionDefines": [
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#if ZENJECT_PRESENT
|
|
2
|
+
namespace WallstopStudios.UnityHelpers.Tests.Integrations.Zenject
|
|
3
|
+
{
|
|
4
|
+
using System.Collections;
|
|
5
|
+
using global::Zenject;
|
|
6
|
+
using NUnit.Framework;
|
|
7
|
+
using UnityEngine;
|
|
8
|
+
using UnityEngine.SceneManagement;
|
|
9
|
+
using UnityEngine.TestTools;
|
|
10
|
+
using WallstopStudios.UnityHelpers.Core.Attributes;
|
|
11
|
+
using WallstopStudios.UnityHelpers.Integrations.Zenject;
|
|
12
|
+
using WallstopStudios.UnityHelpers.Tags;
|
|
13
|
+
using WallstopStudios.UnityHelpers.Tests.Editor.Utils;
|
|
14
|
+
|
|
15
|
+
public sealed class ZenjectRelationalHelpersTests : CommonTestBase
|
|
16
|
+
{
|
|
17
|
+
private sealed class TestComponent : MonoBehaviour
|
|
18
|
+
{
|
|
19
|
+
[ParentComponent(OnlyAncestors = true)]
|
|
20
|
+
public Rigidbody parentBody;
|
|
21
|
+
|
|
22
|
+
[ChildComponent(OnlyDescendants = true)]
|
|
23
|
+
public CapsuleCollider childCollider;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
[Test]
|
|
27
|
+
public void InjectWithRelationsAssignsFields()
|
|
28
|
+
{
|
|
29
|
+
DiContainer container = new DiContainer();
|
|
30
|
+
|
|
31
|
+
// Build hierarchy: Parent(Rigidbody) -> Middle(TestComponent) -> Child(CapsuleCollider)
|
|
32
|
+
GameObject parent = NewGameObject("Root");
|
|
33
|
+
parent.AddComponent<Rigidbody>();
|
|
34
|
+
GameObject middle = NewGameObject("Middle");
|
|
35
|
+
middle.transform.SetParent(parent.transform);
|
|
36
|
+
TestComponent comp = middle.AddComponent<TestComponent>();
|
|
37
|
+
GameObject child = NewGameObject("Child");
|
|
38
|
+
child.transform.SetParent(middle.transform);
|
|
39
|
+
child.AddComponent<CapsuleCollider>();
|
|
40
|
+
|
|
41
|
+
TestComponent result = container.InjectWithRelations(comp);
|
|
42
|
+
|
|
43
|
+
Assert.That(result, Is.SameAs(comp));
|
|
44
|
+
Assert.IsTrue(comp.parentBody != null);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
[Test]
|
|
48
|
+
public void InjectGameObjectWithRelationsAssignsHierarchy()
|
|
49
|
+
{
|
|
50
|
+
DiContainer container = new DiContainer();
|
|
51
|
+
|
|
52
|
+
GameObject root = NewGameObject("Root");
|
|
53
|
+
root.AddComponent<Rigidbody>();
|
|
54
|
+
GameObject middle = NewGameObject("Middle");
|
|
55
|
+
middle.transform.SetParent(root.transform);
|
|
56
|
+
TestComponent comp = middle.AddComponent<TestComponent>();
|
|
57
|
+
GameObject child = NewGameObject("Child");
|
|
58
|
+
child.transform.SetParent(middle.transform);
|
|
59
|
+
child.AddComponent<CapsuleCollider>();
|
|
60
|
+
|
|
61
|
+
container.InjectGameObjectWithRelations(root);
|
|
62
|
+
|
|
63
|
+
Assert.IsTrue(comp.parentBody != null);
|
|
64
|
+
Assert.IsTrue(comp.childCollider != null);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
[Test]
|
|
68
|
+
public void InstantiateGameObjectWithRelationsAssignsHierarchy()
|
|
69
|
+
{
|
|
70
|
+
DiContainer container = new DiContainer();
|
|
71
|
+
|
|
72
|
+
GameObject prefab = NewGameObject("PrefabRoot");
|
|
73
|
+
prefab.AddComponent<Rigidbody>();
|
|
74
|
+
GameObject mid = NewGameObject("PrefabMiddle");
|
|
75
|
+
mid.transform.SetParent(prefab.transform);
|
|
76
|
+
TestComponent prefabComp = mid.AddComponent<TestComponent>();
|
|
77
|
+
GameObject child = NewGameObject("PrefabChild");
|
|
78
|
+
child.transform.SetParent(mid.transform);
|
|
79
|
+
child.AddComponent<CapsuleCollider>();
|
|
80
|
+
|
|
81
|
+
GameObject instance = container.InstantiateGameObjectWithRelations(prefab);
|
|
82
|
+
TestComponent comp = instance.GetComponentInChildren<TestComponent>(true);
|
|
83
|
+
|
|
84
|
+
Assert.IsTrue(comp != null);
|
|
85
|
+
Assert.IsTrue(comp.parentBody != null);
|
|
86
|
+
Assert.IsTrue(comp.childCollider != null);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
[Test]
|
|
90
|
+
public void RelationalMemoryPoolAssignsOnSpawn()
|
|
91
|
+
{
|
|
92
|
+
// Create a pool and inject a container into the private field using reflection
|
|
93
|
+
RelationalMemoryPool<TestComponent> pool = new RelationalMemoryPool<TestComponent>();
|
|
94
|
+
DiContainer container = new DiContainer();
|
|
95
|
+
|
|
96
|
+
pool._container = container;
|
|
97
|
+
|
|
98
|
+
GameObject go = NewGameObject("PooledRoot");
|
|
99
|
+
go.AddComponent<Rigidbody>();
|
|
100
|
+
GameObject middle = NewGameObject("PooledMiddle");
|
|
101
|
+
middle.transform.SetParent(go.transform);
|
|
102
|
+
TestComponent comp = middle.AddComponent<TestComponent>();
|
|
103
|
+
GameObject child = NewGameObject("PooledChild");
|
|
104
|
+
child.transform.SetParent(middle.transform);
|
|
105
|
+
child.AddComponent<CapsuleCollider>();
|
|
106
|
+
|
|
107
|
+
// Call OnSpawned indirectly via protected method using a small helper
|
|
108
|
+
PoolHarness harness = new PoolHarness(pool);
|
|
109
|
+
harness.InvokeOnSpawned(comp);
|
|
110
|
+
|
|
111
|
+
Assert.IsTrue(comp.parentBody != null);
|
|
112
|
+
Assert.IsTrue(comp.childCollider != null);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private sealed class PoolHarness
|
|
116
|
+
{
|
|
117
|
+
private readonly RelationalMemoryPool<TestComponent> _pool;
|
|
118
|
+
|
|
119
|
+
public PoolHarness(RelationalMemoryPool<TestComponent> pool)
|
|
120
|
+
{
|
|
121
|
+
_pool = pool;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public void InvokeOnSpawned(TestComponent item)
|
|
125
|
+
{
|
|
126
|
+
_pool.InternalOnSpawned(item);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
#endif
|
|
@@ -54,6 +54,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Integrations.Zenject
|
|
|
54
54
|
new[] { relationalMetadata }
|
|
55
55
|
);
|
|
56
56
|
cache.ForceRebuildForTests();
|
|
57
|
+
yield return null;
|
|
57
58
|
#endif
|
|
58
59
|
|
|
59
60
|
RelationalComponentAssigner assigner = new RelationalComponentAssigner(cache);
|
|
@@ -68,8 +69,11 @@ namespace WallstopStudios.UnityHelpers.Tests.Integrations.Zenject
|
|
|
68
69
|
|
|
69
70
|
yield return null;
|
|
70
71
|
|
|
71
|
-
Assert.
|
|
72
|
-
Assert.
|
|
72
|
+
Assert.IsTrue(consumer != null);
|
|
73
|
+
Assert.IsTrue(
|
|
74
|
+
consumer.SR != null,
|
|
75
|
+
"Relational field should be assigned by initializer"
|
|
76
|
+
);
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tests.Editor
|
|
2
|
+
{
|
|
3
|
+
#if UNITY_EDITOR
|
|
4
|
+
using NUnit.Framework;
|
|
5
|
+
using UnityEditor;
|
|
6
|
+
using UnityEngine;
|
|
7
|
+
using WallstopStudios.UnityHelpers.Editor;
|
|
8
|
+
|
|
9
|
+
public sealed class PersistentDirectorySettingsTests
|
|
10
|
+
{
|
|
11
|
+
[Test]
|
|
12
|
+
public void GetPathsSortsByCountThenLastUsed()
|
|
13
|
+
{
|
|
14
|
+
PersistentDirectorySettings settings =
|
|
15
|
+
ScriptableObject.CreateInstance<PersistentDirectorySettings>();
|
|
16
|
+
|
|
17
|
+
string tool = "TestTool";
|
|
18
|
+
string ctx = "Context";
|
|
19
|
+
|
|
20
|
+
// B: 1 time, A: 3 times, C: 2 times
|
|
21
|
+
settings.RecordPath(tool, ctx, "Assets/A");
|
|
22
|
+
settings.RecordPath(tool, ctx, "Assets/A");
|
|
23
|
+
settings.RecordPath(tool, ctx, "Assets/A");
|
|
24
|
+
|
|
25
|
+
settings.RecordPath(tool, ctx, "Assets/C");
|
|
26
|
+
settings.RecordPath(tool, ctx, "Assets/C");
|
|
27
|
+
|
|
28
|
+
settings.RecordPath(tool, ctx, "Assets/B");
|
|
29
|
+
|
|
30
|
+
DirectoryUsageData[] paths = settings.GetPaths(tool, ctx);
|
|
31
|
+
Assert.IsNotNull(paths);
|
|
32
|
+
Assert.GreaterOrEqual(paths.Length, 3);
|
|
33
|
+
Assert.AreEqual("Assets/A", paths[0].path);
|
|
34
|
+
Assert.AreEqual("Assets/C", paths[1].path);
|
|
35
|
+
Assert.AreEqual("Assets/B", paths[2].path);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
[Test]
|
|
39
|
+
public void GetPathsTopOnlyRespectsLimit()
|
|
40
|
+
{
|
|
41
|
+
PersistentDirectorySettings settings =
|
|
42
|
+
ScriptableObject.CreateInstance<PersistentDirectorySettings>();
|
|
43
|
+
|
|
44
|
+
string tool = "TopOnlyTool";
|
|
45
|
+
string ctx = "Context";
|
|
46
|
+
|
|
47
|
+
settings.RecordPath(tool, ctx, "Assets/One");
|
|
48
|
+
settings.RecordPath(tool, ctx, "Assets/One");
|
|
49
|
+
settings.RecordPath(tool, ctx, "Assets/Two");
|
|
50
|
+
settings.RecordPath(tool, ctx, "Assets/Three");
|
|
51
|
+
|
|
52
|
+
DirectoryUsageData[] top2 = settings.GetPaths(tool, ctx, topOnly: true, topN: 2);
|
|
53
|
+
Assert.IsNotNull(top2);
|
|
54
|
+
Assert.AreEqual(2, top2.Length);
|
|
55
|
+
Assert.AreEqual("Assets/One", top2[0].path);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
#endif
|
|
59
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tests.Editor
|
|
2
|
+
{
|
|
3
|
+
#if UNITY_EDITOR
|
|
4
|
+
using System;
|
|
5
|
+
using System.Collections.Generic;
|
|
6
|
+
using NUnit.Framework;
|
|
7
|
+
using WallstopStudios.UnityHelpers.Editor;
|
|
8
|
+
|
|
9
|
+
public sealed class PrefabCheckerReportTests
|
|
10
|
+
{
|
|
11
|
+
[Test]
|
|
12
|
+
public void ScanReportConstructorCopiesFolders()
|
|
13
|
+
{
|
|
14
|
+
var report = new PrefabChecker.ScanReport(new[] { "A", "B" });
|
|
15
|
+
string[] folders = report.folders;
|
|
16
|
+
CollectionAssert.AreEqual(new[] { "A", "B" }, folders);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
[Test]
|
|
20
|
+
public void ScanReportAddCopiesMessages()
|
|
21
|
+
{
|
|
22
|
+
var report = new PrefabChecker.ScanReport(Array.Empty<string>());
|
|
23
|
+
report.Add("path.prefab", new List<string> { "m1", "m2" });
|
|
24
|
+
Assert.AreEqual(1, report.items.Count);
|
|
25
|
+
var first = report.items[0];
|
|
26
|
+
Assert.AreEqual("path.prefab", first.path);
|
|
27
|
+
string[] messages = first.messages;
|
|
28
|
+
CollectionAssert.AreEqual(new[] { "m1", "m2" }, messages);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
#endif
|
|
32
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tests.Editor.Sprites
|
|
2
|
+
{
|
|
3
|
+
#if UNITY_EDITOR
|
|
4
|
+
using System;
|
|
5
|
+
using System.Collections.Generic;
|
|
6
|
+
using System.Linq;
|
|
7
|
+
using NUnit.Framework;
|
|
8
|
+
using UnityEngine;
|
|
9
|
+
using WallstopStudios.UnityHelpers.Editor.Sprites;
|
|
10
|
+
|
|
11
|
+
public sealed class AnimationCopierFilterTests
|
|
12
|
+
{
|
|
13
|
+
private static AnimationCopierWindow.AnimationFileInfo NewFileInfo(string name)
|
|
14
|
+
{
|
|
15
|
+
return new AnimationCopierWindow.AnimationFileInfo { FileName = name };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
[Test]
|
|
19
|
+
public void SortsAscendingAndDescending()
|
|
20
|
+
{
|
|
21
|
+
AnimationCopierWindow wnd = ScriptableObject.CreateInstance<AnimationCopierWindow>();
|
|
22
|
+
var a = NewFileInfo("zeta.anim");
|
|
23
|
+
var b = NewFileInfo("alpha.anim");
|
|
24
|
+
var c = NewFileInfo("beta.anim");
|
|
25
|
+
var items = new List<AnimationCopierWindow.AnimationFileInfo> { a, b, c };
|
|
26
|
+
|
|
27
|
+
wnd._filterText = string.Empty;
|
|
28
|
+
wnd._filterUseRegex = false;
|
|
29
|
+
wnd._sortAscending = true;
|
|
30
|
+
var asc = wnd.ApplyFilterAndSort(items).ToList();
|
|
31
|
+
string[] ascNames = asc.Select(o => o.FileName).ToArray();
|
|
32
|
+
CollectionAssert.AreEqual(new[] { "alpha.anim", "beta.anim", "zeta.anim" }, ascNames);
|
|
33
|
+
|
|
34
|
+
wnd._sortAscending = false;
|
|
35
|
+
var desc = wnd.ApplyFilterAndSort(items).ToList();
|
|
36
|
+
string[] descNames = desc.Select(o => o.FileName).ToArray();
|
|
37
|
+
CollectionAssert.AreEqual(new[] { "zeta.anim", "beta.anim", "alpha.anim" }, descNames);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
[Test]
|
|
41
|
+
public void FiltersBySubstringAndRegex()
|
|
42
|
+
{
|
|
43
|
+
AnimationCopierWindow wnd = ScriptableObject.CreateInstance<AnimationCopierWindow>();
|
|
44
|
+
var a = NewFileInfo("walk.anim");
|
|
45
|
+
var b = NewFileInfo("attack.anim");
|
|
46
|
+
var c = NewFileInfo("idle.anim");
|
|
47
|
+
var items = new List<AnimationCopierWindow.AnimationFileInfo> { a, b, c };
|
|
48
|
+
|
|
49
|
+
wnd._filterText = "ta";
|
|
50
|
+
wnd._filterUseRegex = false;
|
|
51
|
+
wnd._sortAscending = true;
|
|
52
|
+
var sub = wnd.ApplyFilterAndSort(items).ToList();
|
|
53
|
+
string[] subNames = sub.Select(o => o.FileName).ToArray();
|
|
54
|
+
CollectionAssert.AreEquivalent(new[] { "attack.anim" }, subNames);
|
|
55
|
+
|
|
56
|
+
wnd._filterText = "^i";
|
|
57
|
+
wnd._filterUseRegex = true;
|
|
58
|
+
var rx = wnd.ApplyFilterAndSort(items).ToList();
|
|
59
|
+
string[] rxNames = rx.Select(o => o.FileName).ToArray();
|
|
60
|
+
CollectionAssert.AreEquivalent(new[] { "idle.anim" }, rxNames);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
#endif
|
|
64
|
+
}
|