import assert from "node:assert/strict"; import test from "node:test"; import { buildFilteredMapTargets, buildMapPresentationFilters, buildSelectableMapEntities, planMapSubjectReveal, primaryMapRuntimeBindings, } from "../apps/catalog/src/mapWorkspaceModel.mjs"; import { mapRuntimeEntityId, mapRuntimeFactKey } from "../apps/catalog/src/mapRuntimeIdentity.mjs"; import { localSectorAtGeodetic, localSectorSummary } from "../apps/catalog/src/mapSectorGrid.mjs"; const origin = { latitude: 55.7558, longitude: 37.6173 }; const gridProfile = { mode: "3d", stepKm: 1, tileSizeKm: 5, graticuleStepDegrees: 1, }; const primaryBinding = { id: "positions", order: 1, dataProductId: "fleet.positions.current.v5", slotId: "points", delivery: "snapshot+patch", semanticTypes: ["map.moving_object"], fieldProjection: ["display_name", "signal_state", "position_source", "object_kind"], presentationProfileId: "moving", }; const joinedBinding = { ...primaryBinding, id: "identity", order: 2, dataProductId: "fleet.units.identity.current.v1", joinToBindingId: "positions", }; const profile = { id: "moving", title: "Current positions", semanticTypes: ["map.moving_object"], label: { mode: "attributes", fields: ["display_name"], maxLength: 80 }, target: { variant: "elevated-spike" }, facets: [{ id: "signal", field: "signal_state", filterable: true, counter: true, values: [ { value: "active", label: "Online", order: 1 }, { value: "inactive", label: "Offline", order: 2 }, ], }], styles: [{ id: "active", color: "#fff", opacity: 1 }], classes: [{ id: "active", label: "Online", priority: 1, match: [], styleId: "active", renderable: true }], defaultClassId: "active", sort: [{ field: "signal_state", order: ["active", "inactive"] }], }; function pointFact(sourceId, signalState, coordinates, displayName = sourceId) { return { sourceId, semanticType: "map.moving_object", observedAt: "2026-08-09T12:00:00.000Z", receivedAt: "2026-08-09T12:00:01.000Z", attributes: { display_name: displayName, signal_state: signalState, position_source: "gelios", object_kind: "tracked_unit", }, geometry: { type: "Point", coordinates }, presentationStatus: signalState, }; } const inactive = pointFact("unit-offline", "inactive", [37.6173, 55.7558], "Альфа"); const active = pointFact("unit-online", "active", [37.618, 55.756], "Янтарь"); const runtimeBindings = [{ bindingId: "positions", dataProductId: primaryBinding.dataProductId, slotId: "points", presentationProfileId: "moving", facts: [inactive, active], cursor: "1", state: "ready", }, { bindingId: "identity", dataProductId: joinedBinding.dataProductId, slotId: "subject-aspect", presentationProfileId: "moving", facts: [active], cursor: "1", state: "ready", }]; test("stable runtime identity uses binding, semantic type and source id only", () => { const moved = { ...active, geometry: { type: "Point", coordinates: [40, 60] } }; assert.equal(mapRuntimeFactKey(active), "map.moving_object\u0000unit-online"); assert.equal(mapRuntimeEntityId("positions", active), mapRuntimeEntityId("positions", moved)); assert.equal(mapRuntimeEntityId("positions", active), "nodedc-runtime:positions:map.moving_object:unit-online"); }); test("joined aspects enrich a primary subject and never become map entities", () => { assert.deepEqual( primaryMapRuntimeBindings(runtimeBindings, [joinedBinding, primaryBinding]).map((binding) => binding.bindingId), ["positions"], ); assert.deepEqual( buildSelectableMapEntities(runtimeBindings, [joinedBinding, primaryBinding], [profile]).map((entity) => entity.fact.sourceId), ["unit-online", "unit-offline"], ); }); test("object overview preserves the same MCP profile sort as facets and renderer", () => { const filters = { positions: { visible: true, facets: {} } }; const selectable = buildSelectableMapEntities(runtimeBindings, [primaryBinding, joinedBinding], [profile]); const targets = buildFilteredMapTargets(runtimeBindings, [primaryBinding, joinedBinding], [profile], filters); // Alphabetical title order would be Альфа, Янтарь. Declared state order is // active, inactive and must remain authoritative everywhere. assert.deepEqual(selectable.map((entity) => entity.title), ["Янтарь", "Альфа"]); assert.deepEqual(targets.map((target) => target.title), ["Янтарь", "Альфа"]); }); test("explicit empty facets remain match-nothing through workspace projection", () => { const subjectStates = { positions: { bindingId: "positions", visible: true, filters: { signal_state: [] }, window: { open: false, rect: { x: 0, y: 0, width: 280, height: 260 }, maximized: false, zIndex: 20 }, }, }; const filters = buildMapPresentationFilters(subjectStates, [primaryBinding], [profile]); assert.deepEqual(filters.positions.facets, { signal_state: [] }); assert.deepEqual(buildFilteredMapTargets(runtimeBindings, [primaryBinding], [profile], filters), []); }); test("search reveal removes only constraints hiding the selected subject", () => { const address = localSectorAtGeodetic(origin, { lod: 1, originLatitude: origin.latitude, originLongitude: origin.longitude, stepMeters: 1_000, }); const selectedSector = { ...localSectorSummary(address, { lod: 1, originLatitude: origin.latitude, originLongitude: origin.longitude, stepMeters: 1_000, tileSizeMeters: 5_000, }), mode: "3d", units: "meters-enu", volume: null, }; const entity = buildSelectableMapEntities(runtimeBindings, [primaryBinding], [profile]) .find((candidate) => candidate.fact.sourceId === "unit-offline"); const subjectState = { bindingId: "positions", visible: false, filters: { signal_state: ["active"] }, window: { open: true, rect: { x: 10, y: 20, width: 280, height: 260 }, maximized: false, zIndex: 42 }, }; const reveal = planMapSubjectReveal({ entity, subjectState, profile, selectedSector, gridProfiles: [gridProfile], origin, hideOutsideSector: true, scope: { excludedBindingIds: ["positions", "other"], excludedProviders: ["gelios", "other"], excludedObjectKinds: ["tracked_unit", "other"], }, }); assert.equal(reveal.subjectState.visible, true); assert.deepEqual(reveal.subjectState.filters, {}); assert.deepEqual(reveal.subjectState.window, subjectState.window); assert.equal(reveal.hideOutsideSector, true); assert.deepEqual(reveal.scope, { excludedBindingIds: ["other"], excludedProviders: ["other"], excludedObjectKinds: ["other"], }); }); test("search keeps the active sector but relaxes outside clipping for an outside subject", () => { const address = localSectorAtGeodetic(origin, { lod: 1, originLatitude: origin.latitude, originLongitude: origin.longitude, stepMeters: 1_000, }); const selectedSector = { ...localSectorSummary(address, { lod: 1, originLatitude: origin.latitude, originLongitude: origin.longitude, stepMeters: 1_000, tileSizeMeters: 5_000, }), mode: "3d", units: "meters-enu", volume: null, }; const outsideEntity = { ...buildSelectableMapEntities(runtimeBindings, [primaryBinding], [profile])[0], fact: pointFact("far-away", "active", [50, 60]), }; const subjectState = { bindingId: "positions", visible: true, filters: {}, window: { open: false, rect: { x: 0, y: 0, width: 280, height: 260 }, maximized: false, zIndex: 20 }, }; const reveal = planMapSubjectReveal({ entity: outsideEntity, subjectState, profile, selectedSector, gridProfiles: [gridProfile], origin, hideOutsideSector: true, scope: { excludedBindingIds: [], excludedProviders: [], excludedObjectKinds: [] }, }); assert.equal(reveal.hideOutsideSector, false); assert.equal(selectedSector.id, reveal ? selectedSector.id : null); });