fix(ui): scope spatial tools to scene

This commit is contained in:
DCCONSTRUCTIONS
2026-07-18 11:31:44 +03:00
parent 574a494759
commit 0ac6424c46
9 changed files with 216 additions and 142 deletions
@@ -1,4 +1,5 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { after, before, test } from "node:test";
import { createServer } from "vite";
@@ -41,7 +42,7 @@ after(async () => {
function wireProfile(overrides = {}) {
return {
version: 1,
version: 2,
revision: 7,
workspace_id: "observation.spatial",
scene_settings: {
@@ -57,12 +58,6 @@ function wireProfile(overrides = {}) {
show_labels: false,
show_camera_frustums: true,
},
tool_windows: {
sources_open: true,
display_open: false,
layers_open: true,
order: ["sources", "layers", "display"],
},
visible_source_ids: ["spatial.point-cloud.live", "camera.left"],
active_floating_source_id: "camera.left",
window_rects: {
@@ -74,14 +69,14 @@ function wireProfile(overrides = {}) {
};
}
test("workspace layout performs a lossless strict wire/camel/wire round trip", () => {
test("workspace layout v2 excludes transient tool-window state", () => {
const wire = wireProfile();
const profile = decodeObservationWorkspaceLayoutProfile(wire);
assert.deepEqual(profile.visibleSourceIds, ["spatial.point-cloud.live", "camera.left"]);
assert.deepEqual(profile.toolWindows.order, ["sources", "layers", "display"]);
assert.equal(profile.sceneSettings.pointSize, 2.5);
assert.deepEqual(encodeObservationWorkspaceLayoutProfile(profile), wire);
assert.equal("tool_windows" in encodeObservationWorkspaceLayoutProfile(profile), false);
});
test("workspace layout rejects unknown fields, transient transport data and invalid schema values", () => {
@@ -93,7 +88,7 @@ test("workspace layout rejects unknown fields, transient transport data and inva
WorkspaceLayoutContractError,
);
assert.throws(
() => decodeObservationWorkspaceLayoutProfile(wireProfile({ version: 2 })),
() => decodeObservationWorkspaceLayoutProfile(wireProfile({ version: 1 })),
/Неподдерживаемая версия/,
);
assert.throws(
@@ -114,15 +109,16 @@ test("workspace layout rejects unknown fields, transient transport data and inva
/выходит за нормализованные границы/,
);
assert.throws(
() => decodeObservationWorkspaceLayoutProfile(wireProfile({
() => decodeObservationWorkspaceLayoutProfile({
...wireProfile(),
tool_windows: {
sources_open: true,
display_open: true,
layers_open: true,
order: ["sources", "sources", "layers"],
order: ["sources", "display", "layers"],
},
})),
/перестановкой/,
}),
/неверная схема/,
);
assert.throws(
() => decodeObservationWorkspaceLayoutProfile(wireProfile({
@@ -227,3 +223,21 @@ test("workspace layout API treats 404 as no profile and exposes revision conflic
error.conflict && error.status === 412 && error.message === "revision mismatch",
);
});
test("scene tool windows are route-scoped and toolbar actions are not duplicated", async () => {
const appSource = await readFile(new URL("../src/App.tsx", import.meta.url), "utf8");
const utilityStart = appSource.indexOf("const contentActions");
const utilityEnd = appSource.indexOf("const header", utilityStart);
const utilitySource = appSource.slice(utilityStart, utilityEnd);
assert.match(appSource, /open=\{spatialWorkspaceActive && sourceWindowOpen\}/);
assert.match(appSource, /open=\{spatialWorkspaceActive && displayWindowOpen\}/);
assert.match(appSource, /open=\{spatialWorkspaceActive && layerInspectorOpen\}/);
assert.match(
appSource,
/if \(spatialWorkspaceActive\) return;[\s\S]*setSceneWindowOrder\(\[\]\)/,
);
assert.doesNotMatch(utilitySource, /Настроить визуальный движок/);
assert.doesNotMatch(utilitySource, /Настроить отображение/);
assert.doesNotMatch(utilitySource, /Открыть слои/);
});