Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions packages/core/src/layout/LayoutEngine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,69 @@ describe('border offset in LayoutEngine', () => {
expect(root.children[0].computed.height).toBe(6);
});
});

describe('CSS Grid auto-placement', () => {
it('places children in a 2-column grid', () => {
const root = makeNode('root', {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
}, [
makeNode('a', { height: 3 }),
makeNode('b', { height: 3 }),
makeNode('c', { height: 3 }),
]);
computeLayout(root, 20, 10);

expect(root.children[0].computed.x).toBe(0);
expect(root.children[0].computed.y).toBe(0);
expect(root.children[1].computed.x).toBe(10);
expect(root.children[1].computed.y).toBe(0);
expect(root.children[2].computed.x).toBe(0);
// Row heights are resolved from gridTemplateRows (1fr default),
// so the third child lands at the start of row 1.
expect(root.children[2].computed.y).toBeGreaterThan(0);
});

it('terminates when rowSpan exceeds available space', () => {
const children = [];
for (let i = 0; i < 5; i++) {
children.push(makeNode(`c${i}`, { height: 10 }));
}
const root = makeNode('root', {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
}, children);

expect(() => computeLayout(root, 20, 10)).not.toThrow();
});
Comment on lines +251 to +262

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The “rowSpan exceeds available space” test does not create a rowSpan case.

Line 251 says rowSpan coverage, but Line 254 only sets height; no gridRowStart/gridRowEnd span is configured, so this does not exercise the intended edge case.

Suggested fix
-    it('terminates when rowSpan exceeds available space', () => {
-        const children = [];
-        for (let i = 0; i < 5; i++) {
-            children.push(makeNode(`c${i}`, { height: 10 }));
-        }
+    it('terminates when rowSpan exceeds available space', () => {
+        const children = [
+            makeNode('oversized', { gridRowStart: 1, gridRowEnd: 'span 5000', height: 1 }),
+            makeNode('next', { height: 1 }),
+        ];
         const root = makeNode('root', {
             display: 'grid',
             gridTemplateColumns: '1fr 1fr',
         }, children);
 
         expect(() => computeLayout(root, 20, 10)).not.toThrow();
+        // Ensure a later placeable child is still processed.
+        expect(root.children[1].computed.width).toBeGreaterThanOrEqual(0);
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('terminates when rowSpan exceeds available space', () => {
const children = [];
for (let i = 0; i < 5; i++) {
children.push(makeNode(`c${i}`, { height: 10 }));
}
const root = makeNode('root', {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
}, children);
expect(() => computeLayout(root, 20, 10)).not.toThrow();
});
it('terminates when rowSpan exceeds available space', () => {
const children = [
makeNode('oversized', { gridRowStart: 1, gridRowEnd: 'span 5000', height: 1 }),
makeNode('next', { height: 1 }),
];
const root = makeNode('root', {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
}, children);
expect(() => computeLayout(root, 20, 10)).not.toThrow();
// Ensure a later placeable child is still processed.
expect(root.children[1].computed.width).toBeGreaterThanOrEqual(0);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/layout/LayoutEngine.test.ts` around lines 251 - 262, The
test titled 'terminates when rowSpan exceeds available space' does not actually
configure a rowSpan case - it only sets height properties on the child nodes
without configuring any grid row spanning attributes. To properly test the
rowSpan edge case, modify one or more of the child nodes created in the children
array to include gridRowStart and gridRowEnd properties that create a span
exceeding the available vertical space of 10 pixels. For example, set
gridRowStart to 1 and gridRowEnd to a value that creates a rowSpan larger than
the available container height to properly exercise the intended edge case
behavior.


it('does not loop forever when grid is completely full', () => {
const root = makeNode('root', {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
}, [
makeNode('a', { gridRowStart: 1, gridRowEnd: 3, gridColumnStart: 1, gridColumnEnd: 2, height: 4 }),
makeNode('b', { gridRowStart: 1, gridRowEnd: 3, gridColumnStart: 2, gridColumnEnd: 3, height: 4 }),
makeNode('c', { height: 2 }),
makeNode('d', { height: 2 }),
]);

expect(() => computeLayout(root, 20, 10)).not.toThrow();
});

it('handles explicit grid positions alongside auto-placement', () => {
const root = makeNode('root', {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
}, [
makeNode('a', { gridRowStart: 1, gridColumnStart: 1, height: 3 }),
makeNode('b', { height: 3 }),
]);
computeLayout(root, 20, 10);

expect(root.children[0].computed.x).toBe(0);
expect(root.children[0].computed.y).toBe(0);
expect(root.children[1].computed.x).toBe(10);
expect(root.children[1].computed.y).toBe(0);
});
});
5 changes: 4 additions & 1 deletion packages/core/src/layout/LayoutEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ function layoutNode(node: LayoutNode, availWidth: number, availHeight: number, p

let currentAutoRow = 0;
let currentAutoCol = 0;
const maxAutoRow = Math.max(numCols * 100, 1000);

for (const child of autoChildren) {
const s = child.style;
Expand All @@ -218,7 +219,8 @@ function layoutNode(node: LayoutNode, availWidth: number, availHeight: number, p

const clampedColSpan = Math.max(1, Math.min(colInfo.span, numCols));

while (true) {
let placed = false;
while (currentAutoRow < maxAutoRow) {
let available = true;
for (let r = currentAutoRow; r < currentAutoRow + rowInfo.span; r++) {
for (let c = currentAutoCol; c < currentAutoCol + clampedColSpan; c++) {
Expand Down Expand Up @@ -254,6 +256,7 @@ function layoutNode(node: LayoutNode, availWidth: number, availHeight: number, p
currentAutoCol = 0;
currentAutoRow++;
}
placed = true;
break;
} else {
currentAutoCol++;
Expand Down
Loading