feat: add configurable view-aware dashboards

This commit is contained in:
2026-07-29 14:32:54 +02:00
parent b28229c003
commit f0732bead2
24 changed files with 3169 additions and 146 deletions

View File

@@ -0,0 +1,170 @@
import assert from "node:assert/strict";
import {
appendPlacement,
dashboardMasonryRowSpan,
defaultDashboardLayout,
layoutUpdatePayload,
reconcileDashboardLayout,
removePlacement,
reorderPlacement
} from "../src/features/dashboard/dashboardLayout.ts";
import { dashboardGridPreview } from "../src/features/dashboard/dashboardEditorTypes.ts";
const widgets = [
{
id: "one",
surfaceId: "one.widget",
title: "One",
moduleId: "one",
defaultSize: "small" as const,
supportedSizes: ["small", "medium"] as const,
defaultConfiguration: { count: 3 },
render: () => null
},
{
id: "two",
surfaceId: "two.widget",
title: "Two",
moduleId: "two",
defaultVisible: false,
defaultSize: "medium" as const,
render: () => null
}
];
const defaults = defaultDashboardLayout(widgets);
assert.deepEqual(defaults.knownWidgetIds, ["one", "two"]);
assert.equal(defaults.placements.length, 1);
assert.equal(defaults.placements[0].widgetId, "one");
assert.deepEqual(defaults.placements[0].configuration, { count: 3 });
const withTwo = appendPlacement(defaults, widgets[1]);
assert.deepEqual(
withTwo.placements.map((placement) => placement.widgetId),
["one", "two"]
);
assert.equal(
appendPlacement(withTwo, widgets[1]).placements.length,
2,
"single-instance widgets must not be duplicated"
);
const reordered = reorderPlacement(
withTwo,
withTwo.placements[1].instanceId,
withTwo.placements[0].instanceId,
"before"
);
assert.deepEqual(
reordered.placements.map((placement) => placement.widgetId),
["two", "one"]
);
const removed = removePlacement(reordered, reordered.placements[0].instanceId);
assert.deepEqual(
removed.placements.map((placement) => placement.widgetId),
["one"]
);
const placementPreview = dashboardGridPreview(
withTwo.placements,
{
kind: "placement",
instanceId: withTwo.placements[0].instanceId
},
{
kind: "placement",
instanceId: withTwo.placements[1].instanceId,
edge: "after"
}
);
assert.deepEqual(
placementPreview.map((item) =>
item.kind === "placement"
? `${item.placement.widgetId}:${item.placeholder}`
: item.widgetId
),
["two:false", "one:true"],
"placement previews must show the prospective order"
);
const cataloguePreview = dashboardGridPreview(
withTwo.placements,
{ kind: "catalogue", widgetId: "three" },
{
kind: "placement",
instanceId: withTwo.placements[0].instanceId,
edge: "after"
},
"wide"
);
assert.deepEqual(
cataloguePreview.map((item) =>
item.kind === "placement"
? item.placement.widgetId
: `${item.widgetId}:${item.size}`
),
["one", "three:wide", "two"],
"catalogue previews must reserve the prospective widget size"
);
const unavailablePlacement = {
instanceId: "unavailable-instance",
widgetId: "temporarily-disabled",
size: "wide" as const,
configuration: { mode: "summary" }
};
const reconciled = reconcileDashboardLayout(
{
layoutVersion: 1,
revision: 4,
knownWidgetIds: ["temporarily-disabled"],
placements: [unavailablePlacement]
},
widgets
);
assert.equal(
reconciled.placements[0],
unavailablePlacement,
"temporarily unavailable contributions must retain their placement"
);
assert.equal(
reconciled.placements[1].widgetId,
"one",
"new default-visible widgets are announced into existing layouts"
);
assert.equal(reconciled.revision, 4);
const payload = layoutUpdatePayload(reconciled);
assert.equal(payload.expected_revision, 4);
assert.equal(payload.placements[0].instance_id, "unavailable-instance");
assert.equal(payload.placements[0].configuration.mode, "summary");
const repeatingWidget = {
...widgets[0],
id: "repeating",
allowMultiple: true,
defaultVisible: false
};
let bounded = defaultDashboardLayout([repeatingWidget]);
for (let index = 0; index < 105; index += 1) {
bounded = appendPlacement(bounded, repeatingWidget);
}
assert.equal(
bounded.placements.length,
100,
"client layout helpers must enforce the server placement limit"
);
assert.equal(
dashboardMasonryRowSpan(142, 4, 18),
40,
"masonry spans must reserve the widget height and inter-widget gap"
);
assert.equal(
dashboardMasonryRowSpan(0, Number.NaN, Number.NaN),
1,
"masonry spans must remain valid while a widget is mounting"
);
console.log("Dashboard layout tests passed.");