@mflrevan/ucp 0.6.2 → 0.6.3
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/README.md +1 -1
- package/bridge/com.ucp.bridge/CHANGELOG.md +48 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +55 -6
- package/bridge/com.ucp.bridge/Editor/Bridge/EditorStateSummary.cs +165 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/EditorStateSummary.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Compatibility/UnityObjectCompat.cs +22 -5
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +1 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +10 -10
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +71 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +2 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectLocator.cs +4 -4
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +3 -3
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +6 -6
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +7 -7
- package/bridge/com.ucp.bridge/Editor/Controllers/RecordingController.cs +8 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +3 -3
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +3 -3
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +5 -5
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Controllers/TransformController.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Controllers/UiController.cs +70 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/UiController.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ViewController.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Ui/UiHost.cs +468 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiHost.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiLintService.cs +554 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiLintService.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiOperationManager.cs +870 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiOperationManager.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioApplier.cs +496 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioApplier.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioLoader.cs +854 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioLoader.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioModels.cs +374 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioModels.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiVisualTreeInspector.cs +690 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiVisualTreeInspector.cs.meta +11 -0
- package/bridge/com.ucp.bridge/{Runtime.meta → Editor/Ui.meta} +1 -1
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +79 -4
- package/bridge/com.ucp.bridge/Tests/Editor/SpatialVisualControllerTests.cs +2 -2
- package/bridge/com.ucp.bridge/Tests/Editor/UiControllerTests.cs +547 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiControllerTests.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiLintServiceTests.cs +253 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiLintServiceTests.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiScenarioTests.cs +587 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiScenarioTests.cs.meta +11 -0
- package/bridge/com.ucp.bridge/package.json +1 -1
- package/package.json +1 -1
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +0 -14
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +0 -7
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
#if UNITY_6000_0_OR_NEWER
|
|
2
|
+
using System;
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using NUnit.Framework;
|
|
5
|
+
using UnityEngine.UIElements;
|
|
6
|
+
|
|
7
|
+
namespace UCP.Bridge.Tests
|
|
8
|
+
{
|
|
9
|
+
public sealed class UiControllerTests
|
|
10
|
+
{
|
|
11
|
+
[Test]
|
|
12
|
+
public void Register_ExposesUiCommandFamily()
|
|
13
|
+
{
|
|
14
|
+
var router = new CommandRouter();
|
|
15
|
+
UiController.Register(router);
|
|
16
|
+
|
|
17
|
+
Assert.That(router.HasMethod("ui/list"), Is.True);
|
|
18
|
+
Assert.That(router.HasMethod("ui/lint"), Is.True);
|
|
19
|
+
Assert.That(router.HasMethod("ui/inspect"), Is.True);
|
|
20
|
+
Assert.That(router.HasMethod("ui/screenshot"), Is.True);
|
|
21
|
+
Assert.That(router.HasMethod("ui/check"), Is.True);
|
|
22
|
+
Assert.That(router.HasMethod("ui/status"), Is.True);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
[Test]
|
|
26
|
+
public void Inspect_ReturnsAsyncStartEnvelopeBeforeResolvingTarget()
|
|
27
|
+
{
|
|
28
|
+
try
|
|
29
|
+
{
|
|
30
|
+
var router = new CommandRouter();
|
|
31
|
+
UiController.Register(router);
|
|
32
|
+
|
|
33
|
+
var response = router.Dispatch(
|
|
34
|
+
"ui/inspect",
|
|
35
|
+
1,
|
|
36
|
+
"{\"target\":\"Assets/DoesNotExist.uxml\"}");
|
|
37
|
+
|
|
38
|
+
Assert.That(response.error, Is.Null);
|
|
39
|
+
var result = (Dictionary<string, object>)response.result;
|
|
40
|
+
Assert.That(result["status"], Is.EqualTo("started"));
|
|
41
|
+
Assert.That(result["operation"], Is.EqualTo("inspect"));
|
|
42
|
+
Assert.That(result["operationId"].ToString(), Does.StartWith("ui-"));
|
|
43
|
+
}
|
|
44
|
+
finally
|
|
45
|
+
{
|
|
46
|
+
// Prevent the intentionally unresolved target from running on a later
|
|
47
|
+
// editor update and polluting the test run's console guard.
|
|
48
|
+
UiOperationManager.ResetForTests();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
[Test]
|
|
53
|
+
public void OperationManager_ConstructorFailureCompletesAndAdvancesQueue()
|
|
54
|
+
{
|
|
55
|
+
UiOperationManager.ResetForTests();
|
|
56
|
+
try
|
|
57
|
+
{
|
|
58
|
+
var failedStart = UiOperationManager.Start("inspect", new Dictionary<string, object>
|
|
59
|
+
{
|
|
60
|
+
["target"] = "Assets/DoesNotExist.uxml",
|
|
61
|
+
["failOnWarnings"] = "not-a-boolean"
|
|
62
|
+
});
|
|
63
|
+
var followingStart = UiOperationManager.Start("inspect", new Dictionary<string, object>
|
|
64
|
+
{
|
|
65
|
+
["failOnWarnings"] = "also-not-a-boolean"
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
UiOperationManager.TickForTests();
|
|
69
|
+
|
|
70
|
+
var failed = UiOperationManager.Status(failedStart["operationId"].ToString());
|
|
71
|
+
Assert.That(failed["status"], Is.EqualTo("failed"));
|
|
72
|
+
var error = (Dictionary<string, object>)failed["error"];
|
|
73
|
+
Assert.That(error["code"], Is.EqualTo("invalid_params"));
|
|
74
|
+
|
|
75
|
+
var following = UiOperationManager.Status(followingStart["operationId"].ToString());
|
|
76
|
+
Assert.That(following["status"], Is.EqualTo("failed"));
|
|
77
|
+
Assert.That(
|
|
78
|
+
((Dictionary<string, object>)following["error"])["code"],
|
|
79
|
+
Is.EqualTo("invalid_params"));
|
|
80
|
+
}
|
|
81
|
+
finally
|
|
82
|
+
{
|
|
83
|
+
UiOperationManager.ResetForTests();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
[Test]
|
|
88
|
+
public void OperationManager_ShutdownFailsActiveAndQueuedOperations()
|
|
89
|
+
{
|
|
90
|
+
UiOperationManager.ResetForTests();
|
|
91
|
+
try
|
|
92
|
+
{
|
|
93
|
+
var active = UiOperationManager.Start("inspect", new Dictionary<string, object>());
|
|
94
|
+
var queued = UiOperationManager.Start("inspect", new Dictionary<string, object>());
|
|
95
|
+
// Activate without advancing initialization (which would resolve a target).
|
|
96
|
+
UiOperationManager.ActivateNextForTests();
|
|
97
|
+
Assert.That(UiOperationManager.Status(active["operationId"].ToString())["status"], Is.EqualTo("running"));
|
|
98
|
+
UiOperationManager.Shutdown();
|
|
99
|
+
foreach (var start in new[] { active, queued })
|
|
100
|
+
{
|
|
101
|
+
var status = UiOperationManager.Status(start["operationId"].ToString());
|
|
102
|
+
Assert.That(status["status"], Is.EqualTo("failed"));
|
|
103
|
+
Assert.That(((Dictionary<string, object>)status["error"])["code"], Is.EqualTo("editor_shutdown"));
|
|
104
|
+
}
|
|
105
|
+
UiOperationManager.TickForTests();
|
|
106
|
+
Assert.That(UiOperationManager.Status(queued["operationId"].ToString())["status"], Is.EqualTo("failed"));
|
|
107
|
+
}
|
|
108
|
+
finally
|
|
109
|
+
{
|
|
110
|
+
UiOperationManager.ResetForTests();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
[Test]
|
|
115
|
+
public void Audit_RetainsFirstErrorsWhenErrorsExhaustBudget()
|
|
116
|
+
{
|
|
117
|
+
var report = new UiApplyReport();
|
|
118
|
+
report.AddDiagnostic("warning", "test", "displaced warning");
|
|
119
|
+
for (var index = 0; index < 201; index++)
|
|
120
|
+
report.AddDiagnostic("error", "test", "error " + index);
|
|
121
|
+
var audit = UiAudit.Run(new VisualElement(), report, Array.Empty<UiCollectionMetadata>());
|
|
122
|
+
Assert.That(audit["errorCount"], Is.EqualTo(201));
|
|
123
|
+
Assert.That(audit["warningCount"], Is.EqualTo(1));
|
|
124
|
+
Assert.That(audit["diagnosticsTruncated"], Is.True);
|
|
125
|
+
Assert.That((List<object>)audit["warnings"], Is.Empty);
|
|
126
|
+
var errors = (List<object>)audit["errors"];
|
|
127
|
+
Assert.That(errors, Has.Count.EqualTo(200));
|
|
128
|
+
Assert.That(((Dictionary<string, object>)errors[199])["message"], Is.EqualTo("error 199"));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
[Test]
|
|
132
|
+
public void Cleanup_RunsEveryActionAndRetainsFirstFailure()
|
|
133
|
+
{
|
|
134
|
+
var visited = new List<int>();
|
|
135
|
+
var expected = new InvalidOperationException("first");
|
|
136
|
+
|
|
137
|
+
var result = UiCleanup.RunAll(
|
|
138
|
+
() =>
|
|
139
|
+
{
|
|
140
|
+
visited.Add(1);
|
|
141
|
+
throw expected;
|
|
142
|
+
},
|
|
143
|
+
() => visited.Add(2),
|
|
144
|
+
() =>
|
|
145
|
+
{
|
|
146
|
+
visited.Add(3);
|
|
147
|
+
throw new InvalidOperationException("later");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
Assert.That(visited, Is.EqualTo(new[] { 1, 2, 3 }));
|
|
151
|
+
Assert.That(result, Is.SameAs(expected));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
[Test]
|
|
155
|
+
public void OperationRecord_PreservesResultWhenCleanupFails()
|
|
156
|
+
{
|
|
157
|
+
var record = new UiOperationRecord(
|
|
158
|
+
"ui-test",
|
|
159
|
+
"inspect",
|
|
160
|
+
new Dictionary<string, object>());
|
|
161
|
+
record.MarkRunning();
|
|
162
|
+
record.Complete(
|
|
163
|
+
new Dictionary<string, object> { ["stateCount"] = 1 },
|
|
164
|
+
UiRenderOperation.BuildError(new UiOperationException(
|
|
165
|
+
"cleanup_failed",
|
|
166
|
+
"cleanup failed")));
|
|
167
|
+
|
|
168
|
+
var envelope = record.Envelope();
|
|
169
|
+
Assert.That(envelope["status"], Is.EqualTo("failed"));
|
|
170
|
+
Assert.That(envelope.ContainsKey("result"), Is.True);
|
|
171
|
+
Assert.That(envelope.ContainsKey("error"), Is.True);
|
|
172
|
+
Assert.That(
|
|
173
|
+
((Dictionary<string, object>)envelope["error"])["code"],
|
|
174
|
+
Is.EqualTo("cleanup_failed"));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
[Test]
|
|
178
|
+
public void CaptureArtifactFileName_DisambiguatesLossyStateTokens()
|
|
179
|
+
{
|
|
180
|
+
var first = UiRenderOperation.CaptureArtifactFileName(
|
|
181
|
+
"Assets/Panel.uxml",
|
|
182
|
+
"a/b",
|
|
183
|
+
0,
|
|
184
|
+
"ui-test");
|
|
185
|
+
var second = UiRenderOperation.CaptureArtifactFileName(
|
|
186
|
+
"Assets/Panel.uxml",
|
|
187
|
+
"a-b",
|
|
188
|
+
1,
|
|
189
|
+
"ui-test");
|
|
190
|
+
|
|
191
|
+
Assert.That(first, Is.EqualTo("Panel-a-b-s0000-ui-test.png"));
|
|
192
|
+
Assert.That(second, Is.EqualTo("Panel-a-b-s0001-ui-test.png"));
|
|
193
|
+
Assert.That(first, Is.Not.EqualTo(second));
|
|
194
|
+
Assert.That(
|
|
195
|
+
UiRenderOperation.CaptureArtifactFileName("Assets/Panel.uxml", "a/b", 0, "ui-test"),
|
|
196
|
+
Is.EqualTo(first));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
[Test]
|
|
200
|
+
public void CaptureArtifactFileName_DropsTheScenarioSuffix()
|
|
201
|
+
{
|
|
202
|
+
Assert.That(
|
|
203
|
+
UiRenderOperation.CaptureArtifactFileName(
|
|
204
|
+
"Assets/UI/Inventory.ucp-ui.json",
|
|
205
|
+
"populated",
|
|
206
|
+
1,
|
|
207
|
+
"ui-test"),
|
|
208
|
+
Is.EqualTo("Inventory-populated-s0001-ui-test.png"));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
[Test]
|
|
212
|
+
public void Inspector_AppliesSimpleQueryAndElementBound()
|
|
213
|
+
{
|
|
214
|
+
var root = new VisualElement { name = "root" };
|
|
215
|
+
var first = new Label("First") { name = "first" };
|
|
216
|
+
first.AddToClassList("match");
|
|
217
|
+
var second = new Label("Second") { name = "second" };
|
|
218
|
+
second.AddToClassList("match");
|
|
219
|
+
root.Add(first);
|
|
220
|
+
root.Add(second);
|
|
221
|
+
|
|
222
|
+
var options = UiInspectOptions.Parse(new Dictionary<string, object>
|
|
223
|
+
{
|
|
224
|
+
["query"] = ".match",
|
|
225
|
+
["detail"] = "summary",
|
|
226
|
+
["max"] = 1L
|
|
227
|
+
});
|
|
228
|
+
var result = UiVisualTreeInspector.Inspect(
|
|
229
|
+
root,
|
|
230
|
+
options,
|
|
231
|
+
Array.Empty<UiCollectionMetadata>());
|
|
232
|
+
|
|
233
|
+
Assert.That(Convert.ToInt32(result["matchCount"]), Is.EqualTo(2));
|
|
234
|
+
Assert.That(Convert.ToInt32(result["returnedElementCount"]), Is.EqualTo(1));
|
|
235
|
+
Assert.That(Convert.ToBoolean(result["truncated"]), Is.True);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
[Test]
|
|
239
|
+
public void InspectorAndGeometrySampler_IncludePhysicalChildrenOfListView()
|
|
240
|
+
{
|
|
241
|
+
var list = new ListView();
|
|
242
|
+
var row = new Label("Before") { name = "row-label" };
|
|
243
|
+
// A ListView owns its physical hierarchy and exposes no contentContainer.
|
|
244
|
+
// Model a realized row without needing a graphics device for this regression.
|
|
245
|
+
list.hierarchy.Add(row);
|
|
246
|
+
Assert.That(list.contentContainer, Is.Null);
|
|
247
|
+
|
|
248
|
+
var snapshot = UiVisualTreeInspector.Inspect(list,
|
|
249
|
+
UiInspectOptions.Parse(new Dictionary<string, object> { ["query"] = "#row-label" }),
|
|
250
|
+
Array.Empty<UiCollectionMetadata>());
|
|
251
|
+
Assert.That(snapshot["matchCount"], Is.EqualTo(1));
|
|
252
|
+
var fullSnapshot = UiVisualTreeInspector.Inspect(list,
|
|
253
|
+
UiInspectOptions.Parse(new Dictionary<string, object> { ["depth"] = 64 }),
|
|
254
|
+
Array.Empty<UiCollectionMetadata>());
|
|
255
|
+
Assert.That(MiniJson.Serialize(fullSnapshot), Does.Contain("row-label"));
|
|
256
|
+
|
|
257
|
+
var before = UiGeometrySampler.Measure(list);
|
|
258
|
+
row.text = "After";
|
|
259
|
+
Assert.That(UiGeometrySampler.Measure(list).Hash, Is.Not.EqualTo(before.Hash));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
[Test]
|
|
263
|
+
public void Audit_ReportsDuplicateStaticNames()
|
|
264
|
+
{
|
|
265
|
+
var root = new VisualElement();
|
|
266
|
+
root.Add(new Label { name = "duplicate" });
|
|
267
|
+
root.Add(new Label { name = "duplicate" });
|
|
268
|
+
|
|
269
|
+
var result = UiAudit.Run(
|
|
270
|
+
root,
|
|
271
|
+
new UiApplyReport(),
|
|
272
|
+
Array.Empty<UiCollectionMetadata>());
|
|
273
|
+
|
|
274
|
+
Assert.That(Convert.ToInt32(result["errorCount"]), Is.EqualTo(0));
|
|
275
|
+
Assert.That(Convert.ToInt32(result["warningCount"]), Is.EqualTo(1));
|
|
276
|
+
var warnings = (List<object>)result["warnings"];
|
|
277
|
+
var warning = (Dictionary<string, object>)warnings[0];
|
|
278
|
+
Assert.That(warning["code"], Is.EqualTo("duplicate_name"));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
[Test]
|
|
282
|
+
public void Audit_UsesOneGlobalDiagnosticBudgetIncludingDuplicateNames()
|
|
283
|
+
{
|
|
284
|
+
var report = new UiApplyReport();
|
|
285
|
+
for (var index = 0; index < 199; index++)
|
|
286
|
+
report.AddDiagnostic("error", "test", "test diagnostic");
|
|
287
|
+
|
|
288
|
+
var root = new VisualElement();
|
|
289
|
+
root.Add(new VisualElement { name = "first-duplicate" });
|
|
290
|
+
root.Add(new VisualElement { name = "first-duplicate" });
|
|
291
|
+
root.Add(new VisualElement { name = "second-duplicate" });
|
|
292
|
+
root.Add(new VisualElement { name = "second-duplicate" });
|
|
293
|
+
|
|
294
|
+
var result = UiAudit.Run(
|
|
295
|
+
root,
|
|
296
|
+
report,
|
|
297
|
+
Array.Empty<UiCollectionMetadata>());
|
|
298
|
+
|
|
299
|
+
Assert.That(Convert.ToInt32(result["errorCount"]), Is.EqualTo(199));
|
|
300
|
+
Assert.That(Convert.ToInt32(result["warningCount"]), Is.EqualTo(2));
|
|
301
|
+
Assert.That((List<object>)result["errors"], Has.Count.EqualTo(199));
|
|
302
|
+
Assert.That((List<object>)result["warnings"], Has.Count.EqualTo(1));
|
|
303
|
+
Assert.That(result["diagnosticsTruncated"], Is.True);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
[TestCase(199)]
|
|
307
|
+
[TestCase(200)]
|
|
308
|
+
[TestCase(201)]
|
|
309
|
+
public void Audit_CountsErrorsBeyondReturnedDiagnosticLimit(int warningCount)
|
|
310
|
+
{
|
|
311
|
+
var report = new UiApplyReport();
|
|
312
|
+
for (var index = 0; index < warningCount; index++)
|
|
313
|
+
report.AddDiagnostic("warning", "test", "warning");
|
|
314
|
+
report.AddDiagnostic("error", "test", "error after warnings");
|
|
315
|
+
|
|
316
|
+
var result = UiAudit.Run(new VisualElement(), report, Array.Empty<UiCollectionMetadata>());
|
|
317
|
+
var errors = (List<object>)result["errors"];
|
|
318
|
+
var warnings = (List<object>)result["warnings"];
|
|
319
|
+
|
|
320
|
+
Assert.That(result["passed"], Is.False);
|
|
321
|
+
Assert.That(result["errorCount"], Is.EqualTo(1));
|
|
322
|
+
Assert.That(result["warningCount"], Is.EqualTo(warningCount));
|
|
323
|
+
Assert.That(errors.Count + warnings.Count, Is.EqualTo(200));
|
|
324
|
+
Assert.That(errors, Has.Count.EqualTo(1));
|
|
325
|
+
Assert.That(((Dictionary<string, object>)errors[0])["message"], Is.EqualTo("error after warnings"));
|
|
326
|
+
Assert.That(warnings, Has.Count.EqualTo(199));
|
|
327
|
+
Assert.That(result["diagnosticsTruncated"], Is.EqualTo(warningCount >= 200));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
[Test]
|
|
331
|
+
public void Audit_ContinuesCheckingTreeAfterApplicationDiagnosticsFillLimit()
|
|
332
|
+
{
|
|
333
|
+
var report = new UiApplyReport();
|
|
334
|
+
for (var index = 0; index < 200; index++)
|
|
335
|
+
report.AddDiagnostic("warning", "test", "warning");
|
|
336
|
+
var root = new VisualElement();
|
|
337
|
+
root.Add(new Label { name = "duplicate" });
|
|
338
|
+
root.Add(new Label { name = "duplicate" });
|
|
339
|
+
|
|
340
|
+
var result = UiAudit.Run(root, report, Array.Empty<UiCollectionMetadata>());
|
|
341
|
+
|
|
342
|
+
Assert.That(result["passed"], Is.True);
|
|
343
|
+
Assert.That(result["errorCount"], Is.EqualTo(0));
|
|
344
|
+
Assert.That(result["warningCount"], Is.EqualTo(201));
|
|
345
|
+
Assert.That((List<object>)result["warnings"], Has.Count.EqualTo(200));
|
|
346
|
+
Assert.That(result["diagnosticsTruncated"], Is.True);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
[Test]
|
|
350
|
+
public void Audit_IgnoresHiddenFocusableElementsAndUnityInternalNames()
|
|
351
|
+
{
|
|
352
|
+
var root = new VisualElement();
|
|
353
|
+
var displayNone = new VisualElement();
|
|
354
|
+
displayNone.style.display = DisplayStyle.None;
|
|
355
|
+
displayNone.Add(new VisualElement { name = "hidden-by-display", focusable = true });
|
|
356
|
+
root.Add(displayNone);
|
|
357
|
+
|
|
358
|
+
var visibilityHidden = new VisualElement();
|
|
359
|
+
visibilityHidden.style.visibility = Visibility.Hidden;
|
|
360
|
+
visibilityHidden.Add(new VisualElement { name = "hidden-by-visibility", focusable = true });
|
|
361
|
+
root.Add(visibilityHidden);
|
|
362
|
+
|
|
363
|
+
root.Add(new VisualElement { name = "unity-generated" });
|
|
364
|
+
root.Add(new VisualElement { name = "unity-generated" });
|
|
365
|
+
root.Add(new VisualElement { name = "authored-name" });
|
|
366
|
+
root.Add(new VisualElement { name = "authored-name" });
|
|
367
|
+
|
|
368
|
+
var result = UiAudit.Run(
|
|
369
|
+
root,
|
|
370
|
+
new UiApplyReport(),
|
|
371
|
+
Array.Empty<UiCollectionMetadata>());
|
|
372
|
+
var warnings = (List<object>)result["warnings"];
|
|
373
|
+
|
|
374
|
+
Assert.That(warnings, Has.Count.EqualTo(1));
|
|
375
|
+
var warning = (Dictionary<string, object>)warnings[0];
|
|
376
|
+
Assert.That(warning["code"], Is.EqualTo("duplicate_name"));
|
|
377
|
+
Assert.That(
|
|
378
|
+
((Dictionary<string, object>)warning["element"])["name"],
|
|
379
|
+
Is.EqualTo("authored-name"));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
[Test]
|
|
383
|
+
public void OperationDurationLimit_IsOverallAndReportsCompletedStates()
|
|
384
|
+
{
|
|
385
|
+
Assert.That(
|
|
386
|
+
UiOperationManager.HasExceededOperationDuration(
|
|
387
|
+
10.0,
|
|
388
|
+
10.0 + UiOperationManager.MaxOperationDurationSeconds),
|
|
389
|
+
Is.False);
|
|
390
|
+
Assert.That(
|
|
391
|
+
UiOperationManager.HasExceededOperationDuration(
|
|
392
|
+
10.0,
|
|
393
|
+
10.001 + UiOperationManager.MaxOperationDurationSeconds),
|
|
394
|
+
Is.True);
|
|
395
|
+
|
|
396
|
+
var error = UiRenderOperation.BuildError(
|
|
397
|
+
UiOperationManager.CreateOperationTimeout("check", 3, 301.0));
|
|
398
|
+
Assert.That(error["code"], Is.EqualTo("timeout"));
|
|
399
|
+
var details = (Dictionary<string, object>)error["details"];
|
|
400
|
+
Assert.That(details["scope"], Is.EqualTo("operation"));
|
|
401
|
+
Assert.That(Convert.ToInt32(details["completedStateCount"]), Is.EqualTo(3));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
[Test]
|
|
405
|
+
public void GeometrySampler_IsDeterministicForUnchangedTree()
|
|
406
|
+
{
|
|
407
|
+
var root = new VisualElement { name = "root" };
|
|
408
|
+
root.Add(new Label("Stable") { name = "label" });
|
|
409
|
+
|
|
410
|
+
var first = UiGeometrySampler.Measure(root);
|
|
411
|
+
var second = UiGeometrySampler.Measure(root);
|
|
412
|
+
|
|
413
|
+
Assert.That(first.Hash, Is.EqualTo(second.Hash));
|
|
414
|
+
Assert.That(first.ElementCount, Is.EqualTo(2));
|
|
415
|
+
Assert.That(first.PanelAttached, Is.False);
|
|
416
|
+
Assert.That(first.IsValid, Is.False);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
[Test]
|
|
420
|
+
public void JsonSanitizer_ReplacesNonFiniteNumbersWithNull()
|
|
421
|
+
{
|
|
422
|
+
var result = (Dictionary<string, object>)UiJsonSanitizer.Sanitize(
|
|
423
|
+
new Dictionary<string, object>
|
|
424
|
+
{
|
|
425
|
+
["nan"] = double.NaN,
|
|
426
|
+
["infinity"] = float.PositiveInfinity,
|
|
427
|
+
["finite"] = 4.25
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
Assert.That(result["nan"], Is.Null);
|
|
431
|
+
Assert.That(result["infinity"], Is.Null);
|
|
432
|
+
Assert.That(result["finite"], Is.EqualTo(4.25));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
[Test]
|
|
436
|
+
public void JsonSanitizer_NormalizesScalarsUnsupportedByMiniJson()
|
|
437
|
+
{
|
|
438
|
+
var result = (Dictionary<string, object>)UiJsonSanitizer.Sanitize(
|
|
439
|
+
new Dictionary<string, object>
|
|
440
|
+
{
|
|
441
|
+
["character"] = 'x',
|
|
442
|
+
["byte"] = (byte)12,
|
|
443
|
+
["signedByte"] = (sbyte)-4,
|
|
444
|
+
["short"] = (short)-123,
|
|
445
|
+
["unsignedShort"] = ushort.MaxValue,
|
|
446
|
+
["unsignedInt"] = uint.MaxValue,
|
|
447
|
+
["unsignedLong"] = ulong.MaxValue,
|
|
448
|
+
["decimal"] = 123.4500m
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
Assert.That(result["character"], Is.EqualTo("x"));
|
|
452
|
+
Assert.That(result["byte"], Is.TypeOf<int>().And.EqualTo(12));
|
|
453
|
+
Assert.That(result["signedByte"], Is.TypeOf<int>().And.EqualTo(-4));
|
|
454
|
+
Assert.That(result["short"], Is.TypeOf<int>().And.EqualTo(-123));
|
|
455
|
+
Assert.That(result["unsignedShort"], Is.TypeOf<int>().And.EqualTo(ushort.MaxValue));
|
|
456
|
+
Assert.That(result["unsignedInt"], Is.TypeOf<long>().And.EqualTo((long)uint.MaxValue));
|
|
457
|
+
Assert.That(result["unsignedLong"], Is.EqualTo(ulong.MaxValue.ToString()));
|
|
458
|
+
Assert.That(result["decimal"], Is.EqualTo("123.4500"));
|
|
459
|
+
|
|
460
|
+
var serialized = MiniJson.Serialize(result);
|
|
461
|
+
Assert.That(serialized, Does.Not.Contain("{}"));
|
|
462
|
+
Assert.That(MiniJson.Deserialize(serialized), Is.TypeOf<Dictionary<string, object>>());
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
[TestCase("#row-label")]
|
|
466
|
+
[TestCase(".card_item-2")]
|
|
467
|
+
[TestCase("Label")]
|
|
468
|
+
[TestCase("UnityEngine.UIElements.Label")]
|
|
469
|
+
public void SimpleQuery_AcceptsOneNameClassOrTypeSelector(string query)
|
|
470
|
+
{
|
|
471
|
+
Assert.DoesNotThrow(() => UiSimpleQuery.Validate(query));
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
[TestCase("#toolbar Button")]
|
|
475
|
+
[TestCase("#a > .b")]
|
|
476
|
+
[TestCase(".a.b")]
|
|
477
|
+
[TestCase("#a#b")]
|
|
478
|
+
[TestCase("Label:hover")]
|
|
479
|
+
[TestCase("#")]
|
|
480
|
+
public void SimpleQuery_RejectsCompoundSelectorsInsteadOfMatchingNothing(string query)
|
|
481
|
+
{
|
|
482
|
+
Assert.Throws<ArgumentException>(() => UiSimpleQuery.Validate(query));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
[Test]
|
|
486
|
+
public void SimpleQuery_MatchesByNameClassShortTypeAndFullType()
|
|
487
|
+
{
|
|
488
|
+
var label = new Label { name = "title" };
|
|
489
|
+
label.AddToClassList("heading");
|
|
490
|
+
|
|
491
|
+
Assert.That(UiSimpleQuery.Matches(label, "#title"), Is.True);
|
|
492
|
+
Assert.That(UiSimpleQuery.Matches(label, ".heading"), Is.True);
|
|
493
|
+
Assert.That(UiSimpleQuery.Matches(label, "label"), Is.True);
|
|
494
|
+
Assert.That(UiSimpleQuery.Matches(label, "UnityEngine.UIElements.Label"), Is.True);
|
|
495
|
+
Assert.That(UiSimpleQuery.Matches(label, "#heading"), Is.False);
|
|
496
|
+
Assert.That(UiSimpleQuery.Matches(label, "Button"), Is.False);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
[Test]
|
|
500
|
+
public void Status_ReportsUnknownOperationsAndRejectsMissingIds()
|
|
501
|
+
{
|
|
502
|
+
var router = new CommandRouter();
|
|
503
|
+
UiController.Register(router);
|
|
504
|
+
|
|
505
|
+
var unknown = router.Dispatch("ui/status", 1, "{\"operationId\":\"ui-does-not-exist\"}");
|
|
506
|
+
Assert.That(unknown.error, Is.Null);
|
|
507
|
+
var result = (Dictionary<string, object>)unknown.result;
|
|
508
|
+
Assert.That(result["found"], Is.False);
|
|
509
|
+
Assert.That(result["operationId"], Is.EqualTo("ui-does-not-exist"));
|
|
510
|
+
|
|
511
|
+
var missing = router.Dispatch("ui/status", 2, "{}");
|
|
512
|
+
Assert.That(missing.error, Is.Not.Null);
|
|
513
|
+
Assert.That(missing.result, Is.Null);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
[Test]
|
|
517
|
+
public void ResultEnvelope_IsSanitizedBeforeItReachesMiniJson()
|
|
518
|
+
{
|
|
519
|
+
var record = new UiOperationRecord("ui-json", "check", new Dictionary<string, object>());
|
|
520
|
+
record.MarkRunning();
|
|
521
|
+
record.Complete(
|
|
522
|
+
new Dictionary<string, object>
|
|
523
|
+
{
|
|
524
|
+
["passed"] = false,
|
|
525
|
+
["errorCount"] = 1,
|
|
526
|
+
["states"] = new List<object>
|
|
527
|
+
{
|
|
528
|
+
new Dictionary<string, object>
|
|
529
|
+
{
|
|
530
|
+
["ratio"] = float.NaN,
|
|
531
|
+
["mode"] = UiCollectionMode.ListView
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
null);
|
|
536
|
+
|
|
537
|
+
var parsed = (Dictionary<string, object>)MiniJson.Deserialize(MiniJson.Serialize(record.Envelope()));
|
|
538
|
+
Assert.That(parsed["status"], Is.EqualTo("completed"));
|
|
539
|
+
var result = (Dictionary<string, object>)parsed["result"];
|
|
540
|
+
Assert.That(result["passed"], Is.False);
|
|
541
|
+
var state = (Dictionary<string, object>)((List<object>)result["states"])[0];
|
|
542
|
+
Assert.That(state["ratio"], Is.Null);
|
|
543
|
+
Assert.That(state["mode"], Is.EqualTo("ListView"));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
#endif
|