226 lines
13 KiB
JavaScript
226 lines
13 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import { test } from "node:test";
|
|
|
|
const sourceRoot = new URL("../src/", import.meta.url);
|
|
|
|
async function read(relativePath) {
|
|
return readFile(new URL(relativePath, sourceRoot), "utf8");
|
|
}
|
|
|
|
test("simulation is a dedicated Polygon workspace with a bounded feature slice", async () => {
|
|
const [productModel, workspaceHub, workspace, catalog, styles] = await Promise.all([
|
|
read("productModel.ts"),
|
|
read("workspaces/Workspaces.tsx"),
|
|
read("workspaces/simulation/SimulationWorkspace.tsx"),
|
|
read("components/simulation/SimulationCatalog.tsx"),
|
|
read("styles.css"),
|
|
]);
|
|
|
|
assert.match(productModel, /id: "simulations"[\s\S]*root: "polygon"[\s\S]*kind: "simulations"/);
|
|
assert.match(workspaceHub, /case "simulations":[\s\S]*<SimulationWorkspace \/>/);
|
|
assert.match(catalog, /simulation-catalog__table/);
|
|
assert.match(workspace, /archivesFromDrop/);
|
|
assert.match(workspace, /uploadSimulationProjectSource/);
|
|
assert.match(catalog, /colSpan=\{3\}/);
|
|
assert.match(catalog, /Ожидает обработки/);
|
|
assert.match(catalog, /Прогресс \$\{roundedPercent\}% \|/);
|
|
assert.match(catalog, /Осталось примерно \$\{minutes\} мин/);
|
|
assert.match(catalog, /simulation-catalog__progress-track/);
|
|
assert.doesNotMatch(catalog, /disabled=\{ACTIVE_STATUSES\.has/);
|
|
assert.match(workspace, /<SimulationViewport[\s\S]*project=\{selected\}/);
|
|
assert.match(workspace, /<ConfirmationModal/);
|
|
assert.match(workspace, /if \(state === "building_streamed_sog"\) return "Сборка LOD"/);
|
|
assert.doesNotMatch(workspace, /\{selected\.provider\.state \?\? selected\.status\}/);
|
|
assert.match(styles, /styles\/simulation\.css/);
|
|
});
|
|
|
|
test("browser upload remains same-origin, resumable and token-free", async () => {
|
|
const [core, window, sourceFiles] = await Promise.all([
|
|
read("core/simulation/projects.ts"),
|
|
read("components/simulation/SimulationProjectWindow.tsx"),
|
|
read("core/simulation/sourceFiles.ts"),
|
|
]);
|
|
|
|
assert.match(core, /const API_ROOT = "\/api\/v1\/simulation-worlds\/projects"/);
|
|
assert.match(core, /method: "HEAD"/);
|
|
assert.match(core, /method: "PATCH"/);
|
|
assert.match(core, /"Upload-Offset"/);
|
|
assert.match(core, /estimatedSecondsRemaining/);
|
|
assert.match(core, /smoothedBytesPerSecond \* 0\.7 \+ currentBytesPerSecond \* 0\.3/);
|
|
assert.match(core, /estimateSimulationProcessing/);
|
|
assert.match(core, /jobCreatedAtUtc/);
|
|
assert.match(core, /stateStartedAtUtc/);
|
|
assert.match(core, /REFERENCE_STREAM_SECONDS = 2_317/);
|
|
assert.doesNotMatch(core, /Authorization|Bearer|18090|Worker 006/);
|
|
assert.match(window, /accept="\.zip,\.rar,\.7z"/);
|
|
assert.match(window, /webkitdirectory/);
|
|
assert.match(sourceFiles, /webkitGetAsEntry/);
|
|
assert.match(sourceFiles, /entry\?\.isDirectory/);
|
|
assert.match(sourceFiles, /const file = item\.getAsFile\(\)/);
|
|
assert.match(sourceFiles, /MAX_SIMULATION_SOURCE_BYTES = 16 \* 1024 \*\* 3/);
|
|
assert.match(sourceFiles, /item\.kind === "file"/);
|
|
assert.match(sourceFiles, /ровно одна сцена \.lcc или \.lcc2/);
|
|
assert.match(sourceFiles, /отдельные архивы ZIP, RAR или 7z/);
|
|
assert.doesNotMatch(window, /Загружаем источник/);
|
|
assert.match(window, /считаем время/);
|
|
});
|
|
|
|
test("failed simulation can restart from its retained Mission Core source", async () => {
|
|
const [core, workspace] = await Promise.all([
|
|
read("core/simulation/projects.ts"),
|
|
read("workspaces/simulation/SimulationWorkspace.tsx"),
|
|
]);
|
|
|
|
assert.match(core, /retrySimulationProject/);
|
|
assert.match(core, /\$\{API_ROOT\}\/\$\{encodeURIComponent\(projectId\)\}\/build/);
|
|
assert.match(workspace, /Повторить сборку/);
|
|
});
|
|
|
|
test("PlayCanvas owns the realtime scene graph without an iframe or React entity tree", async () => {
|
|
const [runtime, ugv, viewport, ugvSettings, projects, styles, packageDocument] = await Promise.all([
|
|
read("components/simulation/PlayCanvasRuntime.ts"),
|
|
read("components/simulation/SimulationUgvController.ts"),
|
|
read("components/simulation/SimulationViewport.tsx"),
|
|
read("components/simulation/SimulationUgvSettingsPanel.tsx"),
|
|
read("core/simulation/projects.ts"),
|
|
read("styles/simulation.css"),
|
|
readFile(new URL("../package.json", import.meta.url), "utf8"),
|
|
]);
|
|
|
|
assert.match(packageDocument, /"playcanvas": "2\.21\.4"/);
|
|
assert.doesNotMatch(packageDocument, /@playcanvas\/react/);
|
|
assert.match(runtime, /new Application\(canvas/);
|
|
assert.match(runtime, /setCanvasFillMode\(FILLMODE_NONE/);
|
|
assert.match(runtime, /setCanvasResolution\(RESOLUTION_AUTO\)/);
|
|
assert.match(runtime, /new Asset\([^,]+, "gsplat"/);
|
|
assert.match(runtime, /camera\.script\?\.create\(CameraControls/);
|
|
assert.match(runtime, /const HOME_POSITION = new Vec3\(0, 1, 0\)/);
|
|
assert.match(runtime, /const HOME_FOCUS = new Vec3\(1, 1, 0\)/);
|
|
assert.match(runtime, /maximum: \{[^}]*lodRangeMin: 0, lodRangeMax: 5, pixelRatio: 2, splatBudget: 4_000_000, targetFps: 60/);
|
|
assert.match(runtime, /app\.scene\.gsplat\.splatBudget = profile\.splatBudget/);
|
|
assert.match(runtime, /app\.autoRender = false/);
|
|
assert.match(runtime, /app\.on\("update", this\.updateFrame\)/);
|
|
assert.match(runtime, /if \(!cameraChanged && this\.controlMode === "free"\) return/);
|
|
assert.doesNotMatch(runtime, /app\.on\("frameupdate"/);
|
|
assert.match(runtime, /WEBGL_lose_context/);
|
|
assert.match(runtime, /selected\.horizontal[^\n]+frame\.mouse\[0\] \*= -1/);
|
|
assert.match(runtime, /selected\.vertical[^\n]+frame\.mouse\[1\] \*= -1/);
|
|
assert.match(runtime, /setCameraInversion/);
|
|
assert.match(runtime, /async setViewMode\([\s\S]*this\.viewMode = mode/);
|
|
assert.doesNotMatch(runtime.match(/async setViewMode\([\s\S]*?\n }/)?.[0] ?? "", /resetCameraInteraction/);
|
|
assert.match(runtime, /controller\.fire\("state"\)/);
|
|
assert.match(runtime, /state\.mouse\.fill\(0\)/);
|
|
assert.match(runtime, /new Asset\([\s\S]*"container"/);
|
|
assert.match(runtime, /instantiateModelEntity/);
|
|
assert.match(runtime, /applyWorldTransform/);
|
|
assert.match(runtime, /setLayerWorldTransform/);
|
|
assert.match(runtime, /playCanvasEulerTransform/);
|
|
assert.match(runtime, /setFromEulerAngles/);
|
|
assert.match(runtime, /DracoDecoderModule/);
|
|
assert.match(runtime, /draco_decoder_gltf\.wasm/);
|
|
assert.match(runtime, /ensureCollision/);
|
|
assert.match(runtime, /setControlMode/);
|
|
assert.match(runtime, /setUgvPreset/);
|
|
assert.match(runtime, /SimulationUgvController/);
|
|
assert.match(runtime, /for \(const model of this\.collisionVisuals\) model\.enabled = visible/);
|
|
assert.match(runtime, /instantiateModelEntity/);
|
|
assert.match(runtime, /this\.ugvController\?\.disable\(\)/);
|
|
assert.match(runtime, /this\.collisionMaterial\.blendType = combined \? BLEND_NORMAL : BLEND_NONE/);
|
|
assert.match(runtime, /private requestRender\(\): void/);
|
|
assert.match(runtime, /app\.root\.addChild/);
|
|
assert.match(runtime, /dispose\(\)/);
|
|
assert.doesNotMatch(`${runtime}\n${viewport}`, /iframe|<GSplat/);
|
|
assert.match(viewport, /Коллизии недоступны/);
|
|
assert.match(viewport, /Для этой сборки слой коллизий не создавался/);
|
|
assert.match(viewport, /<Select[\s\S]*Качество Gaussian-сцены/);
|
|
assert.match(viewport, /label="Режим управления"/);
|
|
assert.match(viewport, /label: "Свободно"/);
|
|
assert.match(viewport, /label: "UGV"/);
|
|
assert.match(viewport, /WASD\/стрелки · пробел — тормоз · R — домой/);
|
|
assert.match(viewport, /data-control-mode=\{controlMode\}/);
|
|
assert.match(viewport, /useState<SimulationQuality>\(project\.viewerSettings\.quality\)/);
|
|
assert.match(viewport, /runtimeRef\.current\?\.home\(\)/);
|
|
assert.match(viewport, /Настройки сцены/);
|
|
assert.match(viewport, /Гауссовый слой/);
|
|
assert.match(viewport, /Слой коллизий/);
|
|
assert.match(viewport, /Инверсия управления по горизонтали/);
|
|
assert.match(viewport, /Инверсия управления по вертикали/);
|
|
assert.match(viewport, /ROTATION_AXES/);
|
|
assert.match(viewport, /<GlassMaterialSurface/);
|
|
assert.match(viewport, /<TextField/);
|
|
assert.match(viewport, /nextDraft\.trim\(\) !== ""/);
|
|
assert.match(viewport, /saveSimulationViewerSettings/);
|
|
assert.match(viewport, /SimulationUgvSettingsPanel/);
|
|
assert.match(viewport, /runtime\.setUgvPreset\(settings\.ugv\)/);
|
|
assert.match(viewport, /onProjectChange\?\.\(saved\)/);
|
|
assert.match(viewport, /const worldManifestRevision = JSON\.stringify\(project\.worldManifest\)/);
|
|
assert.match(viewport, /\[project\.projectId, worldManifestRevision\]/);
|
|
assert.doesNotMatch(viewport, /\[project\.projectId, project\.worldManifest\]/);
|
|
assert.doesNotMatch(viewport, />PlayCanvas|PlayCanvas загружает|Streamed SOG/);
|
|
assert.match(ugv, /AMMO_MODULE_ROOT = "\/vendor\/playcanvas\/ammo\/2026-08-25"/);
|
|
assert.match(ugv, /new this\.ammo\.btRaycastVehicle/);
|
|
assert.match(ugv, /type: "mesh"/);
|
|
assert.match(ugv, /MeshoptSimplifier\.simplify/);
|
|
assert.match(ugv, /PHYSICS_PROXY_MAX_TRIANGLES = 240_000/);
|
|
assert.match(ugv, /createPhysicsProxyMesh/);
|
|
assert.match(ugv, /findSafeSpawnPosition/);
|
|
assert.match(ugv, /physics\.raycastFirst/);
|
|
assert.match(ugv, /runCleanup\("destroy static physics mesh", \(\) => mesh\.destroy\(\)\)/);
|
|
assert.match(ugv, /type: "static"/);
|
|
assert.match(ugv, /mass: this\.settings\.massKg/);
|
|
assert.match(ugv, /suspensionRestLength/);
|
|
assert.match(ugv, /requestedTurn = Number\(this\.isPressed\("KeyA", "ArrowLeft"\)\) - Number\(this\.isPressed\("KeyD", "ArrowRight"\)\)/);
|
|
assert.match(ugv, /leftCommand = clamp\(forwardCommand - turnCommand/);
|
|
assert.match(ugv, /rightCommand = clamp\(forwardCommand \+ turnCommand/);
|
|
assert.match(ugv, /this\.settings\.invertSteering/);
|
|
assert.match(ugv, /this\.settings\.maxSpeedMetersPerSecond/);
|
|
assert.match(ugv, /this\.settings\.maxTurnRateDegrees/);
|
|
assert.match(ugv, /desiredSpeed = forwardInput \* maxSpeed/);
|
|
assert.match(ugv, /maximumAcceleration = clamp\(1\.4 \+ maxSpeed \* 0\.35, 1\.8, 4\.2\)/);
|
|
assert.match(ugv, /SERVICE_BRAKE_DECELERATION_MPS2 = 1\.8/);
|
|
assert.match(ugv, /PARKING_BRAKE_HOLD_DECELERATION_MPS2 = 6/);
|
|
assert.match(ugv, /PARKING_BRAKE_ENGAGE_SPEED_MPS = 0\.08/);
|
|
assert.match(ugv, /TYRE_FRICTION_SLIP = 8\.5/);
|
|
assert.match(ugv, /holding = !braking && forwardInput === 0 && turnInput === 0/);
|
|
assert.match(ugv, /this\.settings\.massKg \* brakeDeceleration/);
|
|
assert.match(ugv, /this\.vehicle\.setBrake\(wheelBrakeForce, index\)/);
|
|
assert.doesNotMatch(ugv, /horizontalSpeed - deceleration \* Math\.max\(0, deltaSeconds\)/);
|
|
assert.match(ugv, /rollingFriction: 0\.12/);
|
|
assert.match(ugv, /angularDamping: 0\.6/);
|
|
assert.match(ugv, /wheel\.set_m_frictionSlip\(TYRE_FRICTION_SLIP\)/);
|
|
assert.doesNotMatch(ugv, /massKg \* 3/);
|
|
assert.match(ugv, /pureTurn = !braking && forwardInput === 0 && turnInput !== 0/);
|
|
assert.match(ugv, /desiredYawRate = -turnInput \* maxTurnRate/);
|
|
assert.match(ugv, /this\.orbitYaw = \(this\.orbitYaw - deltaX \* 0\.006\)/);
|
|
assert.match(ugv, /CAMERA_RETURN_DELAY_SECONDS = 1\.2/);
|
|
assert.match(ugv, /CAMERA_RETURN_DURATION_SECONDS = 2/);
|
|
assert.match(ugv, /approachAngle\(this\.orbitYaw, rearOrbitYaw/);
|
|
assert.match(ugv, /this\.canvas\.addEventListener\("pointerdown", this\.onOrbitPointerDown\)/);
|
|
assert.match(ugv, /this\.canvas\.addEventListener\("wheel", this\.onOrbitWheel/);
|
|
assert.match(ugv, /vehicleGeometryChanged/);
|
|
assert.match(ugv, /removeAction\(this\.vehicle as NativeObject\)/);
|
|
assert.match(ugv, /runCleanup\("destroy static physics proxy", \(\) => entity\.destroy\(\)\)/);
|
|
assert.match(ugv, /runCleanup\("destroy vehicle entity"/);
|
|
assert.match(ugvSettings, /Физика UGV/);
|
|
assert.match(ugvSettings, /Дифференциальный привод/);
|
|
assert.match(ugvSettings, /Масса конструкции/);
|
|
assert.match(ugvSettings, /Общая высота/);
|
|
assert.match(ugvSettings, /Клиренс/);
|
|
assert.match(ugvSettings, /Скорость движения/);
|
|
assert.match(ugvSettings, /Скорость разворота/);
|
|
assert.match(ugvSettings, /exactValueBounds=\{SIMULATION_UGV_EXACT_VALUE_LIMITS\.maxSpeedMetersPerSecond\}/);
|
|
assert.match(projects, /maxSpeedMetersPerSecond: \{ min: 0, max: 1_000_000_000 \}/);
|
|
assert.match(ugvSettings, /Инверсия управления A\/D/);
|
|
assert.match(ugvSettings, /Сохранить пресет/);
|
|
assert.match(ugvSettings, /<RangeControl/);
|
|
assert.match(ugvSettings, /<Switch/);
|
|
assert.match(ugvSettings, /<TextField/);
|
|
assert.match(projects, /missioncore\.simulation-viewer-settings\/v3/);
|
|
assert.match(projects, /ugv: SimulationUgvPreset/);
|
|
assert.match(styles, /\.simulation-viewport__settings \{[\s\S]*grid-template-rows: auto minmax\(0, 1fr\);[\s\S]*overflow: clip;/);
|
|
assert.match(styles, /\.simulation-viewport__settings-scroll \{[\s\S]*overflow-y: auto;/);
|
|
assert.match(styles, /\.simulation-viewport__toolbar \{[\s\S]*position: sticky;[\s\S]*top: 0;/);
|
|
assert.match(styles, /canvas\.is-orbiting[\s\S]*cursor: grabbing/);
|
|
});
|