Verified with the coordinated workspace changes by devkit full run 2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed). This shared UI pass does not mark the individual module reviews complete.
494 lines
19 KiB
TypeScript
494 lines
19 KiB
TypeScript
import {
|
|
dataGridActionTrackMinimum,
|
|
dataGridLayoutSignature,
|
|
dataGridWidthsForLayout,
|
|
distributeDataGridResizeAmount,
|
|
effectiveDataGridColumnMinWidth,
|
|
fitDataGridColumns,
|
|
resizeDataGridColumn,
|
|
type DataGridSizingColumn
|
|
} from "../src/components/table/dataGridSizing";
|
|
|
|
function assertEqual<T>(actual: T, expected: T, message: string): void {
|
|
if (actual !== expected) throw new Error(`${message}: expected ${String(expected)}, got ${String(actual)}`);
|
|
}
|
|
|
|
function assertWidths(
|
|
actual: Record<string, number>,
|
|
expected: Record<string, number>,
|
|
message: string
|
|
): void {
|
|
for (const [columnId, width] of Object.entries(expected)) {
|
|
assertEqual(actual[columnId], width, `${message} (${columnId})`);
|
|
}
|
|
}
|
|
|
|
const minmaxColumn: DataGridSizingColumn = {
|
|
id: "recipients",
|
|
width: "minmax(320px, 1.4fr)",
|
|
resizable: true,
|
|
sortable: true,
|
|
filterable: true
|
|
};
|
|
assertEqual(
|
|
effectiveDataGridColumnMinWidth(minmaxColumn),
|
|
320,
|
|
"a minmax lower bound remains the resize minimum"
|
|
);
|
|
|
|
const weightedColumns: DataGridSizingColumn[] = [
|
|
{ id: "fixed", width: 100 },
|
|
{ id: "primary", width: "minmax(100px, 1fr)", minWidth: 100, maxWidth: 500 },
|
|
{ id: "secondary", width: "minmax(100px, 2fr)", minWidth: 100, maxWidth: 500 }
|
|
];
|
|
const weightedLayout = fitDataGridColumns(weightedColumns, 600);
|
|
assertWidths(
|
|
weightedLayout.widths,
|
|
{ fixed: 100, primary: 200, secondary: 300 },
|
|
"fraction tracks receive residual width by declared weight"
|
|
);
|
|
assertEqual(weightedLayout.underflowWidth, 0, "fraction tracks cover the available container");
|
|
|
|
const boundedLayout = fitDataGridColumns([
|
|
{ id: "fixed", width: 100 },
|
|
{ id: "primary", width: "minmax(100px, 1fr)", minWidth: 100, maxWidth: 150 },
|
|
{ id: "secondary", width: "minmax(100px, 2fr)", minWidth: 100, maxWidth: 200 }
|
|
], 600);
|
|
assertWidths(
|
|
boundedLayout.widths,
|
|
{ fixed: 250, primary: 150, secondary: 200 },
|
|
"real columns absorb cover space after fraction tracks reach their preferred maxima"
|
|
);
|
|
assertEqual(boundedLayout.underflowWidth, 0, "cover layouts never create a synthetic filler track");
|
|
|
|
const overflowLayout = fitDataGridColumns([
|
|
{ id: "fixed", width: 200 },
|
|
{ id: "primary", width: "minmax(320px, 1fr)", resizable: true },
|
|
{ id: "secondary", width: "minmax(260px, 1fr)", resizable: true }
|
|
], 600);
|
|
assertWidths(
|
|
overflowLayout.widths,
|
|
{ fixed: 72, primary: 320, secondary: 260 },
|
|
"ordinary fixed tracks also shrink before a cover layout overflows"
|
|
);
|
|
assertEqual(overflowLayout.overflowWidth, 52, "hard minimum-width excess remains horizontal overflow");
|
|
|
|
const percentageLayout = fitDataGridColumns([
|
|
{ id: "fixed", width: 100 },
|
|
{ id: "primary", width: "30%", minWidth: 100 },
|
|
{ id: "secondary", width: "20%", minWidth: 100 }
|
|
], 1000);
|
|
assertWidths(
|
|
percentageLayout.widths,
|
|
{ fixed: 100, primary: 540, secondary: 360 },
|
|
"percentage tracks absorb residual cover space proportionally"
|
|
);
|
|
assertEqual(percentageLayout.underflowWidth, 0, "percentage layouts cover the complete container");
|
|
|
|
const overriddenLayout = fitDataGridColumns(weightedColumns, 800, {}, { primary: 450 });
|
|
assertWidths(
|
|
overriddenLayout.widths,
|
|
{ fixed: 100, primary: 450, secondary: 250 },
|
|
"a user-resized track stays fixed while responsive peers absorb container changes"
|
|
);
|
|
|
|
const overriddenOverflowLayout = fitDataGridColumns(weightedColumns, 500, {}, { primary: 450 });
|
|
assertWidths(
|
|
overriddenOverflowLayout.widths,
|
|
{ fixed: 72, primary: 450, secondary: 100 },
|
|
"a cover layout preserves an explicit user width under viewport pressure"
|
|
);
|
|
assertEqual(
|
|
overriddenOverflowLayout.overflowWidth,
|
|
122,
|
|
"intentional user width may remain horizontally scrollable"
|
|
);
|
|
|
|
const overriddenUnderflowLayout = fitDataGridColumns(
|
|
weightedColumns,
|
|
800,
|
|
{},
|
|
{ fixed: 80, primary: 200, secondary: 200 }
|
|
);
|
|
assertEqual(
|
|
overriddenUnderflowLayout.underflowWidth,
|
|
0,
|
|
"a persisted cover layout is expanded when it would otherwise underflow"
|
|
);
|
|
|
|
const clampedOverrideLayout = fitDataGridColumns(weightedColumns, 800, {}, { primary: 900 }, "free");
|
|
assertEqual(
|
|
clampedOverrideLayout.widths.primary,
|
|
500,
|
|
"free-layout user overrides remain bounded by the column maximum"
|
|
);
|
|
|
|
const redistributedGrowth = distributeDataGridResizeAmount(300, [
|
|
{ columnId: "bounded", startWidth: 100, minWidth: 100, maxWidth: 150, weight: 1 },
|
|
{ columnId: "remaining", startWidth: 100, minWidth: 100, maxWidth: 500, weight: 1 }
|
|
]);
|
|
assertWidths(
|
|
redistributedGrowth.amounts,
|
|
{ bounded: 50, remaining: 250 },
|
|
"unused growth is redistributed after a peer reaches its maximum"
|
|
);
|
|
assertEqual(redistributedGrowth.applied, 300, "bounded weighted growth applies the complete available amount");
|
|
|
|
const boundedShrink = distributeDataGridResizeAmount(-300, [
|
|
{ columnId: "primary", startWidth: 300, minWidth: 200, maxWidth: 500, weight: 1 },
|
|
{ columnId: "secondary", startWidth: 300, minWidth: 250, maxWidth: 500, weight: 1 }
|
|
]);
|
|
assertWidths(
|
|
boundedShrink.amounts,
|
|
{ primary: -100, secondary: -50 },
|
|
"shrink distribution stops every peer at its minimum"
|
|
);
|
|
assertEqual(boundedShrink.applied, -150, "unavailable shrink remains unapplied instead of squashing columns");
|
|
|
|
const legacyFillLayout = fitDataGridColumns([
|
|
{ id: "fixed", width: 100 },
|
|
{ id: "fill", width: 200, minWidth: 120, maxWidth: 450, fill: true }
|
|
], 500);
|
|
assertWidths(
|
|
legacyFillLayout.widths,
|
|
{ fixed: 100, fill: 400 },
|
|
"legacy fill columns remain stable under the explicit sizing contract"
|
|
);
|
|
|
|
const fixedCoverColumns: DataGridSizingColumn[] = [
|
|
{ id: "first", width: 100 },
|
|
{ id: "second", width: 200 },
|
|
{ id: "actions", width: 80, sticky: "end" }
|
|
];
|
|
const fixedCoverLayout = fitDataGridColumns(fixedCoverColumns, 760);
|
|
assertWidths(
|
|
fixedCoverLayout.widths,
|
|
{ first: 226.67, second: 453.33, actions: 80 },
|
|
"all-fixed cover grids stretch ordinary columns proportionally"
|
|
);
|
|
assertEqual(fixedCoverLayout.underflowWidth, 0, "all-fixed cover grids leave no blank track");
|
|
|
|
const softMaximumLayout = fitDataGridColumns([
|
|
{ id: "first", width: 100, maxWidth: 100 },
|
|
{ id: "second", width: 200, maxWidth: 200 },
|
|
{ id: "actions", width: 80, sticky: "end" }
|
|
], 680);
|
|
assertWidths(
|
|
softMaximumLayout.widths,
|
|
{ first: 200, second: 400, actions: 80 },
|
|
"coverage may exceed preferred maxima instead of rendering a filler column"
|
|
);
|
|
|
|
const resizeColumns: DataGridSizingColumn[] = [
|
|
{ id: "first", width: 200, minWidth: 100, maxWidth: 500, resizable: true },
|
|
{ id: "middle", width: 300, minWidth: 100, maxWidth: 500, resizable: true },
|
|
{ id: "last", width: 300, minWidth: 100, maxWidth: 500, resizable: true },
|
|
{ id: "actions", width: 100, sticky: "end" }
|
|
];
|
|
const resizeBase = { first: 200, middle: 300, last: 300, actions: 100 };
|
|
const resizedFirst = resizeDataGridColumn(resizeColumns, resizeBase, "first", 120, "cover");
|
|
assertWidths(
|
|
resizedFirst.widths,
|
|
{ first: 320, middle: 300, last: 300, actions: 100 },
|
|
"growing an earlier cover column may create horizontal overflow"
|
|
);
|
|
|
|
const shrunkFirst = resizeDataGridColumn(resizeColumns, resizeBase, "first", -80, "cover");
|
|
assertWidths(
|
|
shrunkFirst.widths,
|
|
{ first: 120, middle: 340, last: 340, actions: 100 },
|
|
"shrinking an earlier cover column grows resizable columns to its right"
|
|
);
|
|
|
|
const resizedLast = resizeDataGridColumn(resizeColumns, resizeBase, "last", 120, "cover");
|
|
assertWidths(
|
|
resizedLast.widths,
|
|
{ first: 200, middle: 300, last: 420, actions: 100 },
|
|
"the last resizable cover column may grow into horizontal overflow"
|
|
);
|
|
|
|
const committedLayout = fitDataGridColumns(
|
|
resizeColumns,
|
|
900,
|
|
{},
|
|
resizedLast.widths,
|
|
"cover"
|
|
);
|
|
assertWidths(
|
|
committedLayout.widths,
|
|
resizedLast.widths,
|
|
"committing a cover resize preserves intentional overflow without a mouse-up snap"
|
|
);
|
|
|
|
const contractedUserLayout = fitDataGridColumns(
|
|
resizeColumns,
|
|
700,
|
|
{},
|
|
resizedLast.widths,
|
|
"cover",
|
|
900
|
|
);
|
|
assertEqual(
|
|
contractedUserLayout.overflowWidth,
|
|
120,
|
|
"container contraction retains only the overflow deliberately selected by the user"
|
|
);
|
|
assertEqual(
|
|
Object.values(contractedUserLayout.widths).reduce((total, width) => total + width, 0),
|
|
820,
|
|
"persisted tracks contract with their container instead of retaining the first pixel layout"
|
|
);
|
|
assertEqual(
|
|
Object.entries(contractedUserLayout.widths).every(([columnId, width]) => {
|
|
const column = resizeColumns.find((candidate) => candidate.id === columnId);
|
|
return Boolean(column && width >= effectiveDataGridColumnMinWidth(column));
|
|
}),
|
|
true,
|
|
"responsive user layouts retain every hard column minimum"
|
|
);
|
|
|
|
const contractedCoveredLayout = fitDataGridColumns(
|
|
resizeColumns,
|
|
700,
|
|
{},
|
|
resizeBase,
|
|
"cover",
|
|
900
|
|
);
|
|
assertEqual(
|
|
contractedCoveredLayout.overflowWidth,
|
|
0,
|
|
"an exact-cover user layout continues to cover a narrower container without avoidable overflow"
|
|
);
|
|
assertEqual(
|
|
Object.values(contractedCoveredLayout.widths).reduce((total, width) => total + width, 0),
|
|
700,
|
|
"an exact-cover layout shrinks to the newly available width"
|
|
);
|
|
|
|
const stoppedLastShrink = resizeDataGridColumn(
|
|
resizeColumns,
|
|
resizeBase,
|
|
"last",
|
|
-120,
|
|
"cover"
|
|
);
|
|
assertWidths(
|
|
stoppedLastShrink.widths,
|
|
resizeBase,
|
|
"the last resizable column stops before its shrink would require a filler"
|
|
);
|
|
assertEqual(
|
|
stoppedLastShrink.appliedDelta,
|
|
0,
|
|
"the stopped last-column shrink reports no applied movement"
|
|
);
|
|
|
|
const overflowedResizeBase = { first: 200, middle: 300, last: 420, actions: 100 };
|
|
const consumedLastOverflow = resizeDataGridColumn(
|
|
resizeColumns,
|
|
overflowedResizeBase,
|
|
"last",
|
|
-180,
|
|
"cover",
|
|
120
|
|
);
|
|
assertWidths(
|
|
consumedLastOverflow.widths,
|
|
{ first: 200, middle: 300, last: 300, actions: 100 },
|
|
"the last resizable column consumes overflow and stops at exact coverage"
|
|
);
|
|
assertEqual(
|
|
consumedLastOverflow.appliedDelta,
|
|
-120,
|
|
"last-column shrink is clamped to the available overflow"
|
|
);
|
|
|
|
const stillPastLastStop = resizeDataGridColumn(
|
|
resizeColumns,
|
|
overflowedResizeBase,
|
|
"last",
|
|
-150,
|
|
"cover",
|
|
120
|
|
);
|
|
assertWidths(
|
|
stillPastLastStop.widths,
|
|
consumedLastOverflow.widths,
|
|
"reversing the pointer while it remains beyond the stop does not move the column"
|
|
);
|
|
const crossedLastStop = resizeDataGridColumn(
|
|
resizeColumns,
|
|
overflowedResizeBase,
|
|
"last",
|
|
-110,
|
|
"cover",
|
|
120
|
|
);
|
|
assertEqual(
|
|
crossedLastStop.widths.last,
|
|
310,
|
|
"the column resumes only after the pointer crosses the cover boundary"
|
|
);
|
|
|
|
const fixedPeerResize = resizeDataGridColumn([
|
|
{ id: "editable", width: 200, minWidth: 100, maxWidth: 500, resizable: true },
|
|
{ id: "fixed", width: 300 },
|
|
{ id: "actions", width: 100, sticky: "end" }
|
|
], { editable: 200, fixed: 300, actions: 100 }, "editable", 80, "cover");
|
|
assertWidths(
|
|
fixedPeerResize.widths,
|
|
{ editable: 280, fixed: 300, actions: 100 },
|
|
"growth does not resize a fixed peer merely to avoid overflow"
|
|
);
|
|
|
|
const mixedPeerColumns: DataGridSizingColumn[] = [
|
|
{ id: "first", width: 200, minWidth: 100, maxWidth: 500, resizable: true },
|
|
{ id: "fixed", width: 120, minWidth: 120, maxWidth: 120 },
|
|
{ id: "last", width: 300, minWidth: 100, maxWidth: 500, resizable: true },
|
|
{ id: "actions", width: 100, sticky: "end" }
|
|
];
|
|
const mixedPeerBase = { first: 200, fixed: 120, last: 300, actions: 100 };
|
|
for (const mode of ["cover", "free", "constrained"] as const) {
|
|
const grown = resizeDataGridColumn(mixedPeerColumns, mixedPeerBase, "first", 80, mode);
|
|
assertWidths(grown.widths,
|
|
{ first: 280, fixed: 120, last: mode === "constrained" ? 220 : 300, actions: 100 },
|
|
`${mode} growth crosses a fixed intermediate column without changing it`);
|
|
const shrunk = resizeDataGridColumn(mixedPeerColumns, mixedPeerBase, "first", -80, mode);
|
|
assertWidths(shrunk.widths,
|
|
{ first: 120, fixed: 120, last: mode === "free" ? 300 : 380, actions: 100 },
|
|
`${mode} shrink uses only the appropriate resizable compensation peers`);
|
|
const limited = resizeDataGridColumn(mixedPeerColumns, mixedPeerBase, "first", 800, mode);
|
|
assertEqual(limited.widths.fixed, 120, `${mode} exhaustion never changes the fixed intermediate track`);
|
|
assertEqual(limited.widths.first, mode === "constrained" ? 400 : 500, `${mode} retains declared growth limits`);
|
|
}
|
|
|
|
const preferredLimitColumns: DataGridSizingColumn[] = [
|
|
{ id: "recipients", width: "minmax(320px, 1.4fr)", preferredMaxWidth: 640, resizable: true },
|
|
{ id: "active", width: 130 },
|
|
{ id: "delivery", width: "minmax(260px, 0.9fr)", preferredMaxWidth: 480, resizable: true },
|
|
{ id: "attachments", width: 180 },
|
|
...["first", "second", "third"].map((id) => ({ id, width: 190, minWidth: 160, preferredMaxWidth: 360, resizable: true })),
|
|
{ id: "actions", width: 180, sticky: "end" }
|
|
];
|
|
const hardLimitColumns = preferredLimitColumns.map(({ preferredMaxWidth, ...column }) => ({ ...column, maxWidth: preferredMaxWidth }));
|
|
for (const container of [900, 1600, 2400, 3085]) {
|
|
assertWidths(fitDataGridColumns(preferredLimitColumns, container).widths,
|
|
fitDataGridColumns(hardLimitColumns, container).widths,
|
|
`preferred caps preserve the previous automatic fit at ${container}px without imposing manual caps`);
|
|
}
|
|
const rightPeersAtOldCaps: DataGridSizingColumn[] = [
|
|
{ id: "recipients", width: "minmax(320px, 1.4fr)", preferredMaxWidth: 640, resizable: true },
|
|
{ id: "fixed", width: 130, minWidth: 130, maxWidth: 130 },
|
|
{ id: "first", width: 190, preferredMaxWidth: 360, resizable: true },
|
|
{ id: "second", width: 190, preferredMaxWidth: 360, resizable: true },
|
|
{ id: "actions", width: 180, sticky: "end" }
|
|
];
|
|
const oversizedRecipient = { recipients: 1000, fixed: 130, first: 360, second: 360, actions: 180 };
|
|
const oldManualCaps = rightPeersAtOldCaps.map(({ preferredMaxWidth, ...column }) => ({
|
|
...column, maxWidth: preferredMaxWidth ?? column.maxWidth
|
|
}));
|
|
assertEqual(resizeDataGridColumn(oldManualCaps, oversizedRecipient, "recipients", -120, "cover", 0).appliedDelta, 0,
|
|
"regression: at the scrolled-right boundary, old field caps block shrinking an oversized recipient column");
|
|
const shrunkAcrossPreferredCaps = resizeDataGridColumn(rightPeersAtOldCaps, oversizedRecipient, "recipients", -120, "cover", 0);
|
|
assertWidths(shrunkAcrossPreferredCaps.widths,
|
|
{ recipients: 880, fixed: 130, first: 420, second: 420, actions: 180 },
|
|
"preferred field caps allow cover compensation at the right scroll boundary without moving fixed neighbors");
|
|
assertWidths(fitDataGridColumns(rightPeersAtOldCaps, 2030, {}, shrunkAcrossPreferredCaps.widths, "cover", 2030).widths,
|
|
shrunkAcrossPreferredCaps.widths, "committing a compensated shrink never refills the wide recipient column");
|
|
for (const mode of ["cover", "free", "constrained"] as const) {
|
|
const expandedField = resizeDataGridColumn(rightPeersAtOldCaps, oversizedRecipient, "first", 160, mode);
|
|
assertEqual(expandedField.widths.first, 520, `${mode} direct resizing can exceed a presentation-only field cap`);
|
|
assertEqual(expandedField.widths.fixed, 130, `${mode} preferred caps do not change a fixed neighbor`);
|
|
}
|
|
const preferredWithHardLimit: DataGridSizingColumn[] = [{ id: "text", width: 300, preferredMaxWidth: 360, maxWidth: 500, resizable: true }];
|
|
assertEqual(resizeDataGridColumn(preferredWithHardLimit, { text: 360 }, "text", 200, "cover").widths.text, 500,
|
|
"an explicitly declared hard maximum still bounds direct resizing beyond a preferred cap");
|
|
assertEqual(fitDataGridColumns([{ id: "text", width: 500, preferredMaxWidth: 400, maxWidth: 300 }], 300, {}, {}, "free").widths.text, 300,
|
|
"a preferred cap never overrides a smaller hard maximum");
|
|
assertEqual(fitDataGridColumns([{ id: "text", width: 300, minWidth: 160, preferredMaxWidth: 100 }], 160, {}, {}, "free").widths.text, 160,
|
|
"preferred caps never reduce a column below its hard minimum");
|
|
assertEqual(dataGridLayoutSignature([{ id: "text", width: 190, resizable: true }], "container", "cover"),
|
|
"v3::text:190:::r::::default:::container::cover", "omitting preferred caps preserves the existing persisted signature format");
|
|
assertEqual(dataGridLayoutSignature(preferredLimitColumns, "container", "cover") === dataGridLayoutSignature(hardLimitColumns, "container", "cover"), false,
|
|
"new preferred sizing declarations invalidate only their own obsolete manual-width contract");
|
|
|
|
const coverBeyondPassiveMax = resizeDataGridColumn([
|
|
{ id: "first", width: 200, minWidth: 100, maxWidth: 200, resizable: true },
|
|
{ id: "second", width: 300, minWidth: 100, maxWidth: 300, resizable: true },
|
|
{ id: "actions", width: 100, sticky: "end" }
|
|
], { first: 200, second: 300, actions: 100 }, "first", -80, "cover");
|
|
assertWidths(
|
|
coverBeyondPassiveMax.widths,
|
|
{ first: 200, second: 300, actions: 100 },
|
|
"cover shrinking stops when right-side peers have reached their maximum"
|
|
);
|
|
|
|
const constrainedAtPassiveMax = resizeDataGridColumn([
|
|
{ id: "first", width: 200, minWidth: 100, maxWidth: 200, resizable: true },
|
|
{ id: "second", width: 300, minWidth: 100, maxWidth: 300, resizable: true },
|
|
{ id: "actions", width: 100, sticky: "end" }
|
|
], { first: 200, second: 300, actions: 100 }, "first", -80, "constrained");
|
|
assertWidths(
|
|
constrainedAtPassiveMax.widths,
|
|
{ first: 200, second: 300, actions: 100 },
|
|
"constrained compensation stops when every peer is at its hard maximum"
|
|
);
|
|
|
|
const initiallyExpandedColumn = resizeDataGridColumn([
|
|
{ id: "first", width: 100, minWidth: 72, maxWidth: 100, resizable: true },
|
|
{ id: "second", width: 200, minWidth: 72, resizable: true }
|
|
], { first: 180, second: 320 }, "first", 20, "cover");
|
|
assertEqual(
|
|
initiallyExpandedColumn.widths.first,
|
|
180,
|
|
"a cover-expanded active track does not snap backwards on a growth drag"
|
|
);
|
|
|
|
const freeResize = resizeDataGridColumn(
|
|
resizeColumns,
|
|
resizeBase,
|
|
"last",
|
|
120,
|
|
"free"
|
|
);
|
|
assertWidths(
|
|
freeResize.widths,
|
|
{ first: 200, middle: 300, last: 420, actions: 100 },
|
|
"free resizing changes only the active track"
|
|
);
|
|
|
|
const originalSignature = dataGridLayoutSignature(weightedColumns, "container", "cover");
|
|
const changedSignature = dataGridLayoutSignature(
|
|
weightedColumns.map((column) => column.id === "primary" ? { ...column, maxWidth: 420 } : column),
|
|
"container",
|
|
"cover"
|
|
);
|
|
assertEqual(
|
|
dataGridWidthsForLayout(originalSignature, originalSignature, { primary: 450 })?.primary,
|
|
450,
|
|
"matching layout signatures restore user widths"
|
|
);
|
|
assertEqual(
|
|
dataGridWidthsForLayout(originalSignature, changedSignature, { primary: 450 }),
|
|
undefined,
|
|
"stale layout signatures discard user widths"
|
|
);
|
|
assertEqual(
|
|
dataGridLayoutSignature(weightedColumns, "container", "free") === originalSignature,
|
|
false,
|
|
"free and cover layouts keep independent user overrides"
|
|
);
|
|
assertEqual(
|
|
originalSignature.startsWith("v3::"),
|
|
true,
|
|
"the responsive persistence contract invalidates legacy hard-width snapshots"
|
|
);
|
|
|
|
assertEqual(dataGridActionTrackMinimum({ id: "actions", width: 72 }, 182, 800), 182, "four action slots override a clipped preferred width");
|
|
assertEqual(dataGridActionTrackMinimum({ id: "actions" }, 182, 280), 140, "narrow action groups leave room to read row data and wrap");
|
|
assertEqual(dataGridActionTrackMinimum({ id: "actions", minWidth: 160 }, 182, 280), 160, "declared action hard minima remain authoritative");
|
|
assertEqual(dataGridActionTrackMinimum({ id: "actions" }, Number.NaN, 400), 72, "invalid content measurements cannot poison tracks");
|
|
assertEqual(dataGridLayoutSignature([{ id: "first", filterable: true }], "container", "cover")
|
|
=== dataGridLayoutSignature([{ id: "first" }], "container", "cover"), false, "changed header controls invalidate obsolete persisted minima");
|