fix(dashboard): stabilize four-column widget placement

This commit is contained in:
2026-07-30 15:36:59 +02:00
parent f0732bead2
commit 49afc6d73d
10 changed files with 491 additions and 72 deletions

View File

@@ -2,13 +2,18 @@ import assert from "node:assert/strict";
import {
appendPlacement,
dashboardMasonryRowSpan,
dashboardWidgetColumnSpan,
defaultDashboardLayout,
layoutUpdatePayload,
normalizeDashboardColumnStart,
reconcileDashboardLayout,
removePlacement,
reorderPlacement
} from "../src/features/dashboard/dashboardLayout.ts";
import { dashboardGridPreview } from "../src/features/dashboard/dashboardEditorTypes.ts";
import {
dashboardColumnStartFromPointer,
dashboardGridPreview
} from "../src/features/dashboard/dashboardEditorTypes.ts";
const widgets = [
{
@@ -36,6 +41,7 @@ const defaults = defaultDashboardLayout(widgets);
assert.deepEqual(defaults.knownWidgetIds, ["one", "two"]);
assert.equal(defaults.placements.length, 1);
assert.equal(defaults.placements[0].widgetId, "one");
assert.equal(defaults.placements[0].columnStart, 1);
assert.deepEqual(defaults.placements[0].configuration, { count: 3 });
const withTwo = appendPlacement(defaults, widgets[1]);
@@ -43,6 +49,11 @@ assert.deepEqual(
withTwo.placements.map((placement) => placement.widgetId),
["one", "two"]
);
assert.equal(
withTwo.placements[1].columnStart,
2,
"new widgets should use the next available horizontal position"
);
assert.equal(
appendPlacement(withTwo, widgets[1]).placements.length,
2,
@@ -53,12 +64,19 @@ const reordered = reorderPlacement(
withTwo,
withTwo.placements[1].instanceId,
withTwo.placements[0].instanceId,
"before"
"before",
3
);
assert.deepEqual(
reordered.placements.map((placement) => placement.widgetId),
["two", "one"]
);
assert.equal(
reordered.placements[0].columnStart,
3,
"moving a widget must change its column without moving other columns"
);
assert.equal(reordered.placements[1].columnStart, 1);
const removed = removePlacement(reordered, reordered.placements[0].instanceId);
assert.deepEqual(
@@ -75,7 +93,8 @@ const placementPreview = dashboardGridPreview(
{
kind: "placement",
instanceId: withTwo.placements[1].instanceId,
edge: "after"
edge: "after",
columnStart: 3
}
);
assert.deepEqual(
@@ -87,6 +106,13 @@ assert.deepEqual(
["two:false", "one:true"],
"placement previews must show the prospective order"
);
assert.equal(
placementPreview[1].kind === "placement"
? placementPreview[1].placement.columnStart
: null,
3,
"placement previews must show the prospective column"
);
const cataloguePreview = dashboardGridPreview(
withTwo.placements,
@@ -94,7 +120,8 @@ const cataloguePreview = dashboardGridPreview(
{
kind: "placement",
instanceId: withTwo.placements[0].instanceId,
edge: "after"
edge: "after",
columnStart: 2
},
"wide"
);
@@ -107,11 +134,19 @@ assert.deepEqual(
["one", "three:wide", "two"],
"catalogue previews must reserve the prospective widget size"
);
assert.equal(
cataloguePreview[1].kind === "catalogue"
? cataloguePreview[1].columnStart
: null,
2,
"catalogue previews must reserve the selected horizontal position"
);
const unavailablePlacement = {
instanceId: "unavailable-instance",
widgetId: "temporarily-disabled",
size: "wide" as const,
columnStart: 2,
configuration: { mode: "summary" }
};
const reconciled = reconcileDashboardLayout(
@@ -124,8 +159,8 @@ const reconciled = reconcileDashboardLayout(
widgets
);
assert.equal(
reconciled.placements[0],
unavailablePlacement,
reconciled.placements[0].columnStart,
2,
"temporarily unavailable contributions must retain their placement"
);
assert.equal(
@@ -138,8 +173,29 @@ 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].column_start, 2);
assert.equal(payload.placements[0].configuration.mode, "summary");
assert.equal(dashboardWidgetColumnSpan("small"), 1);
assert.equal(dashboardWidgetColumnSpan("medium"), 2);
assert.equal(dashboardWidgetColumnSpan("wide"), 3);
assert.equal(dashboardWidgetColumnSpan("full"), 4);
assert.equal(
normalizeDashboardColumnStart("medium", 4),
3,
"column starts must be clamped so the widget stays in the four-column grid"
);
assert.equal(
dashboardColumnStartFromPointer(500, 0, 1000, 20, 2, 4),
2,
"a two-column widget must be placeable in the middle columns"
);
assert.equal(
dashboardColumnStartFromPointer(950, 0, 1000, 20, 3, 4),
2,
"a three-column widget must be placeable from column two"
);
const repeatingWidget = {
...widgets[0],
id: "repeating",