mirror of
https://github.com/penpot/penpot.git
synced 2026-08-23 13:18:36 +00:00
* 🐛 Fix problem with padding types in plugins * 🐛 Fix problem with multiple selection paddings
638 lines
22 KiB
TypeScript
638 lines
22 KiB
TypeScript
import { expect } from '../framework/expect';
|
|
import { describe, test } from '../framework/registry';
|
|
import type { Board } from '@penpot/plugin-types';
|
|
import type { TestContext } from '../framework/types';
|
|
|
|
// Layout (flex & grid).
|
|
// Layouts are created on boards via addFlexLayout/addGridLayout. Child and cell
|
|
// properties are reached through a shape that lives inside the laid-out board.
|
|
// Each group keeps its happy-path round-trips together with the related edge
|
|
// cases: "throws" tests assert invalid input is rejected (a red test surfaces a
|
|
// missing-validation bug) and the remaining ones pin non-trivial valid behaviour.
|
|
// Note: track insertion indices are 0-based (addRowAtIndex/removeRow/setRow);
|
|
// appendChild cell coordinates and layoutCell.row/column are 1-based.
|
|
|
|
function board(ctx: TestContext): Board {
|
|
const b = ctx.penpot.createBoard();
|
|
ctx.board.appendChild(b);
|
|
return b;
|
|
}
|
|
|
|
describe('Layout', () => {
|
|
describe('Flex', () => {
|
|
test('addFlexLayout adds a flex layout to the board', (ctx) => {
|
|
const b = board(ctx);
|
|
const flex = b.addFlexLayout();
|
|
expect(flex).toBeDefined();
|
|
expect(b.flex).toBeDefined();
|
|
});
|
|
|
|
test('direction and wrap round-trip', (ctx) => {
|
|
const flex = board(ctx).addFlexLayout();
|
|
flex.dir = 'column';
|
|
flex.wrap = 'wrap';
|
|
expect(flex.dir).toBe('column');
|
|
expect(flex.wrap).toBe('wrap');
|
|
});
|
|
|
|
test('alignment round-trips', (ctx) => {
|
|
const flex = board(ctx).addFlexLayout();
|
|
flex.alignItems = 'center';
|
|
flex.alignContent = 'space-between';
|
|
flex.justifyItems = 'center';
|
|
flex.justifyContent = 'space-around';
|
|
expect(flex.alignItems).toBe('center');
|
|
expect(flex.alignContent).toBe('space-between');
|
|
expect(flex.justifyItems).toBe('center');
|
|
expect(flex.justifyContent).toBe('space-around');
|
|
});
|
|
|
|
test('gaps and padding round-trip', (ctx) => {
|
|
const flex = board(ctx).addFlexLayout();
|
|
flex.rowGap = 5;
|
|
flex.columnGap = 10;
|
|
flex.verticalPadding = 4;
|
|
flex.horizontalPadding = 8;
|
|
flex.topPadding = 1;
|
|
flex.rightPadding = 2;
|
|
flex.bottomPadding = 3;
|
|
flex.leftPadding = 4;
|
|
expect(flex.rowGap).toBeCloseTo(5, 0);
|
|
expect(flex.columnGap).toBeCloseTo(10, 0);
|
|
expect(flex.topPadding).toBeCloseTo(1, 0);
|
|
expect(flex.rightPadding).toBeCloseTo(2, 0);
|
|
expect(flex.bottomPadding).toBeCloseTo(3, 0);
|
|
expect(flex.leftPadding).toBeCloseTo(4, 0);
|
|
});
|
|
|
|
// Gap and padding setters accept fractional numbers, not just integers.
|
|
test('gaps and padding accept fractional values', (ctx) => {
|
|
const flex = board(ctx).addFlexLayout();
|
|
flex.rowGap = 5.5;
|
|
flex.columnGap = 10.25;
|
|
flex.topPadding = 1.5;
|
|
flex.rightPadding = 2.25;
|
|
flex.bottomPadding = 3.75;
|
|
flex.leftPadding = 4.5;
|
|
expect(flex.rowGap).toBeCloseTo(5.5, 2);
|
|
expect(flex.columnGap).toBeCloseTo(10.25, 2);
|
|
expect(flex.topPadding).toBeCloseTo(1.5, 2);
|
|
expect(flex.rightPadding).toBeCloseTo(2.25, 2);
|
|
expect(flex.bottomPadding).toBeCloseTo(3.75, 2);
|
|
expect(flex.leftPadding).toBeCloseTo(4.5, 2);
|
|
});
|
|
|
|
// paddingType is "simple" (sides mirrored) or "multiple" (each side independent).
|
|
test('paddingType round-trips', (ctx) => {
|
|
const flex = board(ctx).addFlexLayout();
|
|
expect(flex.paddingType).toBe('simple');
|
|
flex.paddingType = 'multiple';
|
|
expect(flex.paddingType).toBe('multiple');
|
|
flex.paddingType = 'simple';
|
|
expect(flex.paddingType).toBe('simple');
|
|
});
|
|
|
|
// Issue #10278: a single asymmetric side must switch paddingType to "multiple" so it paints.
|
|
test('setting an individual padding side switches paddingType to multiple', (ctx) => {
|
|
const flex = board(ctx).addFlexLayout();
|
|
expect(flex.paddingType).toBe('simple');
|
|
flex.leftPadding = 40;
|
|
expect(flex.leftPadding).toBeCloseTo(40, 0);
|
|
expect(flex.paddingType).toBe('multiple');
|
|
});
|
|
|
|
// Re-derived from the sides: once symmetric again, the type collapses to "simple".
|
|
test('padding collapsing back to symmetric restores simple type', (ctx) => {
|
|
const flex = board(ctx).addFlexLayout();
|
|
flex.leftPadding = 40;
|
|
expect(flex.paddingType).toBe('multiple');
|
|
flex.leftPadding = 0;
|
|
expect(flex.paddingType).toBe('simple');
|
|
});
|
|
|
|
test('sizing round-trips', (ctx) => {
|
|
const flex = board(ctx).addFlexLayout();
|
|
flex.horizontalSizing = 'fix';
|
|
flex.verticalSizing = 'auto';
|
|
expect(flex.horizontalSizing).toBe('fix');
|
|
expect(flex.verticalSizing).toBe('auto');
|
|
});
|
|
|
|
test('appendChild adds a child to the flex layout', (ctx) => {
|
|
const b = board(ctx);
|
|
const flex = b.addFlexLayout();
|
|
const rect = ctx.penpot.createRectangle();
|
|
flex.appendChild(rect);
|
|
expect(b.children.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('remove deletes the flex layout', (ctx) => {
|
|
const b = board(ctx);
|
|
const flex = b.addFlexLayout();
|
|
flex.remove();
|
|
expect(b.flex).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
describe('Grid', () => {
|
|
test('addGridLayout adds a grid layout to the board', (ctx) => {
|
|
const b = board(ctx);
|
|
const grid = b.addGridLayout();
|
|
expect(grid).toBeDefined();
|
|
expect(b.grid).toBeDefined();
|
|
});
|
|
|
|
test('direction round-trips', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.dir = 'row';
|
|
expect(grid.dir).toBe('row');
|
|
});
|
|
|
|
test('rows and columns can be added and read as tracks', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
grid.addColumn('percent', 50);
|
|
|
|
expect(grid.rows.length).toBeGreaterThan(0);
|
|
expect(grid.columns.length).toBeGreaterThan(0);
|
|
expect(grid.rows[0].type).toBe('flex');
|
|
expect(grid.columns[0].type).toBe('percent');
|
|
expect(grid.columns[0].value).toBeCloseTo(50, 0);
|
|
});
|
|
|
|
test('addRowAtIndex inserts a row at an index', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
grid.addRowAtIndex(0, 'fixed', 100);
|
|
expect(grid.rows[0].type).toBe('fixed');
|
|
});
|
|
|
|
test('addColumnAtIndex inserts a column at an index', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addColumn('flex', 1);
|
|
grid.addColumnAtIndex(0, 'fixed', 100);
|
|
expect(grid.columns[0].type).toBe('fixed');
|
|
});
|
|
|
|
test('setRow and setColumn update tracks', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
grid.addColumn('flex', 1);
|
|
grid.setRow(0, 'fixed', 80);
|
|
grid.setColumn(0, 'percent', 25);
|
|
expect(grid.rows[0].type).toBe('fixed');
|
|
expect(grid.rows[0].value).toBeCloseTo(80, 0);
|
|
expect(grid.columns[0].type).toBe('percent');
|
|
});
|
|
|
|
test('removeRow and removeColumn drop tracks', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
grid.addRow('flex', 1);
|
|
grid.addColumn('flex', 1);
|
|
grid.addColumn('flex', 1);
|
|
const rowsBefore = grid.rows.length;
|
|
const colsBefore = grid.columns.length;
|
|
grid.removeRow(0);
|
|
grid.removeColumn(0);
|
|
expect(grid.rows.length).toBe(rowsBefore - 1);
|
|
expect(grid.columns.length).toBe(colsBefore - 1);
|
|
});
|
|
|
|
test('appendChild places a child into a cell', (ctx) => {
|
|
const b = board(ctx);
|
|
const grid = b.addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
grid.addColumn('flex', 1);
|
|
const rect = ctx.penpot.createRectangle();
|
|
grid.appendChild(rect, 1, 1);
|
|
expect(b.children.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('alignment and gaps round-trip', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.alignItems = 'center';
|
|
grid.justifyItems = 'start';
|
|
grid.rowGap = 7;
|
|
grid.columnGap = 9;
|
|
expect(grid.alignItems).toBe('center');
|
|
expect(grid.justifyItems).toBe('start');
|
|
expect(grid.rowGap).toBeCloseTo(7, 0);
|
|
expect(grid.columnGap).toBeCloseTo(9, 0);
|
|
});
|
|
|
|
// Gap and padding setters accept fractional numbers, not just integers.
|
|
test('gaps and padding accept fractional values', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.rowGap = 7.5;
|
|
grid.columnGap = 9.25;
|
|
grid.topPadding = 1.5;
|
|
grid.rightPadding = 2.75;
|
|
grid.bottomPadding = 3.25;
|
|
grid.leftPadding = 4.5;
|
|
expect(grid.rowGap).toBeCloseTo(7.5, 2);
|
|
expect(grid.columnGap).toBeCloseTo(9.25, 2);
|
|
expect(grid.topPadding).toBeCloseTo(1.5, 2);
|
|
expect(grid.rightPadding).toBeCloseTo(2.75, 2);
|
|
expect(grid.bottomPadding).toBeCloseTo(3.25, 2);
|
|
expect(grid.leftPadding).toBeCloseTo(4.5, 2);
|
|
});
|
|
|
|
// paddingType behaves the same as on flex layouts (see issue #10278).
|
|
test('paddingType round-trips', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
expect(grid.paddingType).toBe('simple');
|
|
grid.paddingType = 'multiple';
|
|
expect(grid.paddingType).toBe('multiple');
|
|
});
|
|
|
|
test('setting an individual padding side switches paddingType to multiple', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
expect(grid.paddingType).toBe('simple');
|
|
grid.leftPadding = 40;
|
|
expect(grid.leftPadding).toBeCloseTo(40, 0);
|
|
expect(grid.paddingType).toBe('multiple');
|
|
});
|
|
|
|
// Index boundaries — invalid indices must be rejected.
|
|
test('addRowAtIndex with a negative index throws', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
expect(() => grid.addRowAtIndex(-1, 'fixed', 100)).toThrow();
|
|
});
|
|
|
|
test('addRowAtIndex past the end throws', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
expect(() => grid.addRowAtIndex(5, 'fixed', 100)).toThrow();
|
|
});
|
|
|
|
test('addColumnAtIndex with a negative index throws', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addColumn('flex', 1);
|
|
expect(() => grid.addColumnAtIndex(-1, 'fixed', 100)).toThrow();
|
|
});
|
|
|
|
test('removeRow on an empty grid throws', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
expect(() => grid.removeRow(0)).toThrow();
|
|
});
|
|
|
|
test('removeRow with an out-of-range index throws', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
expect(() => grid.removeRow(5)).toThrow();
|
|
});
|
|
|
|
test('removeColumn with an out-of-range index throws', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addColumn('flex', 1);
|
|
expect(() => grid.removeColumn(5)).toThrow();
|
|
});
|
|
|
|
test('setRow with an out-of-range index throws', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
expect(() => grid.setRow(5, 'fixed', 80)).toThrow();
|
|
});
|
|
|
|
test('setColumn with an out-of-range index throws', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addColumn('flex', 1);
|
|
expect(() => grid.setColumn(5, 'fixed', 80)).toThrow();
|
|
});
|
|
|
|
// Track type — invalid track types must be rejected.
|
|
test('addRow with an invalid track type throws', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
expect(() => grid.addRow('not-a-type' as unknown as 'flex', 1)).toThrow();
|
|
});
|
|
|
|
test('addColumn with an invalid track type throws', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
expect(() =>
|
|
grid.addColumn('not-a-type' as unknown as 'flex', 1),
|
|
).toThrow();
|
|
});
|
|
|
|
// Success edges — non-trivial valid behaviour.
|
|
test('addRowAtIndex inserts at the position and shifts the rest', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addRow('fixed', 10);
|
|
grid.addRow('percent', 20);
|
|
grid.addRowAtIndex(1, 'flex', 1);
|
|
expect(grid.rows.length).toBe(3);
|
|
expect(grid.rows[0].type).toBe('fixed');
|
|
expect(grid.rows[1].type).toBe('flex');
|
|
expect(grid.rows[2].type).toBe('percent');
|
|
});
|
|
|
|
test('setRow updates a track in place without changing the count', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
grid.addRow('flex', 1);
|
|
grid.addRow('flex', 1);
|
|
grid.setRow(1, 'fixed', 80);
|
|
expect(grid.rows.length).toBe(3);
|
|
expect(grid.rows[0].type).toBe('flex');
|
|
expect(grid.rows[1].type).toBe('fixed');
|
|
expect(grid.rows[1].value).toBeCloseTo(80, 0);
|
|
expect(grid.rows[2].type).toBe('flex');
|
|
});
|
|
|
|
test('mixed track types coexist and read back', (ctx) => {
|
|
const grid = board(ctx).addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
grid.addRow('fixed', 50);
|
|
grid.addRow('percent', 25);
|
|
grid.addRow('auto');
|
|
expect(grid.rows.map((r) => r.type)).toEqual([
|
|
'flex',
|
|
'fixed',
|
|
'percent',
|
|
'auto',
|
|
]);
|
|
});
|
|
|
|
test('appendChild places children into the cells requested', (ctx) => {
|
|
const b = board(ctx);
|
|
const grid = b.addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
grid.addRow('flex', 1);
|
|
grid.addColumn('flex', 1);
|
|
grid.addColumn('flex', 1);
|
|
|
|
const a = ctx.penpot.createRectangle();
|
|
const c = ctx.penpot.createRectangle();
|
|
grid.appendChild(a, 1, 1);
|
|
grid.appendChild(c, 2, 2);
|
|
|
|
const cellA = a.layoutCell;
|
|
const cellC = c.layoutCell;
|
|
expect(cellA).toBeDefined();
|
|
expect(cellC).toBeDefined();
|
|
if (cellA && cellC) {
|
|
expect(cellA.row).toBeCloseTo(1, 0);
|
|
expect(cellA.column).toBeCloseTo(1, 0);
|
|
expect(cellC.row).toBeCloseTo(2, 0);
|
|
expect(cellC.column).toBeCloseTo(2, 0);
|
|
}
|
|
});
|
|
|
|
test('a grid board can nest a flex board as a child', (ctx) => {
|
|
const outer = board(ctx);
|
|
const grid = outer.addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
grid.addColumn('flex', 1);
|
|
|
|
const inner = ctx.penpot.createBoard();
|
|
const flex = inner.addFlexLayout();
|
|
flex.dir = 'column';
|
|
grid.appendChild(inner, 1, 1);
|
|
|
|
expect(outer.children.length).toBeGreaterThan(0);
|
|
expect(inner.flex).toBeDefined();
|
|
expect(inner.flex && inner.flex.dir).toBe('column');
|
|
});
|
|
});
|
|
|
|
describe('Child', () => {
|
|
test('layout child properties round-trip', (ctx) => {
|
|
const b = board(ctx);
|
|
const flex = b.addFlexLayout();
|
|
const rect = ctx.penpot.createRectangle();
|
|
flex.appendChild(rect);
|
|
|
|
const child = rect.layoutChild;
|
|
expect(child).toBeDefined();
|
|
if (child) {
|
|
child.absolute = true;
|
|
child.zIndex = 3;
|
|
child.horizontalSizing = 'fill';
|
|
child.verticalSizing = 'fix';
|
|
child.alignSelf = 'center';
|
|
child.horizontalMargin = 2;
|
|
child.verticalMargin = 4;
|
|
child.topMargin = 1;
|
|
child.rightMargin = 2;
|
|
child.bottomMargin = 3;
|
|
child.leftMargin = 4;
|
|
child.maxWidth = 200;
|
|
child.maxHeight = 150;
|
|
child.minWidth = 10;
|
|
child.minHeight = 20;
|
|
|
|
expect(child.absolute).toBe(true);
|
|
expect(child.zIndex).toBeCloseTo(3, 0);
|
|
expect(child.horizontalSizing).toBe('fill');
|
|
expect(child.verticalSizing).toBe('fix');
|
|
expect(child.alignSelf).toBe('center');
|
|
expect(child.topMargin).toBeCloseTo(1, 0);
|
|
expect(child.maxWidth).toBeCloseTo(200, 0);
|
|
expect(child.minHeight).toBeCloseTo(20, 0);
|
|
}
|
|
});
|
|
|
|
// marginType is the child-margin counterpart of a layout's paddingType.
|
|
test('marginType round-trips', (ctx) => {
|
|
const b = board(ctx);
|
|
const flex = b.addFlexLayout();
|
|
const rect = ctx.penpot.createRectangle();
|
|
flex.appendChild(rect);
|
|
const child = rect.layoutChild;
|
|
expect(child).toBeDefined();
|
|
if (child) {
|
|
expect(child.marginType).toBe('simple');
|
|
child.marginType = 'multiple';
|
|
expect(child.marginType).toBe('multiple');
|
|
child.marginType = 'simple';
|
|
expect(child.marginType).toBe('simple');
|
|
}
|
|
});
|
|
|
|
// Issue #10278 (margins): a single asymmetric side must switch marginType to "multiple".
|
|
test('setting an individual margin side switches marginType to multiple', (ctx) => {
|
|
const b = board(ctx);
|
|
const flex = b.addFlexLayout();
|
|
const rect = ctx.penpot.createRectangle();
|
|
flex.appendChild(rect);
|
|
const child = rect.layoutChild;
|
|
expect(child).toBeDefined();
|
|
if (child) {
|
|
expect(child.marginType).toBe('simple');
|
|
child.leftMargin = 12;
|
|
expect(child.leftMargin).toBeCloseTo(12, 0);
|
|
expect(child.marginType).toBe('multiple');
|
|
}
|
|
});
|
|
|
|
// Symmetric margins collapse the child back to "simple", mirroring padding.
|
|
test('margin collapsing back to symmetric restores simple type', (ctx) => {
|
|
const b = board(ctx);
|
|
const flex = b.addFlexLayout();
|
|
const rect = ctx.penpot.createRectangle();
|
|
flex.appendChild(rect);
|
|
const child = rect.layoutChild;
|
|
expect(child).toBeDefined();
|
|
if (child) {
|
|
child.leftMargin = 12;
|
|
expect(child.marginType).toBe('multiple');
|
|
child.leftMargin = 0;
|
|
expect(child.marginType).toBe('simple');
|
|
}
|
|
});
|
|
|
|
// Community report (forum #10700, issue #12): layoutChild was said to be
|
|
// null for children appended to a flex board that is re-found (fresh
|
|
// proxy) instead of using the creation-time reference. Did not reproduce;
|
|
// kept as a regression pin.
|
|
test('layoutChild is available on children of a re-found flex board', (ctx) => {
|
|
const b = board(ctx);
|
|
b.resize(300, 200);
|
|
b.addFlexLayout();
|
|
|
|
const found = ctx.penpot.currentPage.getShapeById(b.id) as Board;
|
|
expect(found).not.toBeNull();
|
|
const child = ctx.penpot.createRectangle();
|
|
found.appendChild(child);
|
|
expect(child.layoutChild).toBeDefined();
|
|
if (child.layoutChild) {
|
|
child.layoutChild.horizontalSizing = 'fill';
|
|
expect(child.layoutChild.horizontalSizing).toBe('fill');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Cell', () => {
|
|
test('layout cell properties round-trip', (ctx) => {
|
|
const b = board(ctx);
|
|
const grid = b.addGridLayout();
|
|
grid.addRow('flex', 1);
|
|
grid.addRow('flex', 1);
|
|
grid.addColumn('flex', 1);
|
|
grid.addColumn('flex', 1);
|
|
const rect = ctx.penpot.createRectangle();
|
|
grid.appendChild(rect, 1, 1);
|
|
|
|
const cell = rect.layoutCell;
|
|
expect(cell).toBeDefined();
|
|
if (cell) {
|
|
cell.row = 1;
|
|
cell.column = 1;
|
|
cell.rowSpan = 1;
|
|
cell.columnSpan = 2;
|
|
expect(cell.row).toBeCloseTo(1, 0);
|
|
expect(cell.column).toBeCloseTo(1, 0);
|
|
expect(cell.columnSpan).toBeCloseTo(2, 0);
|
|
}
|
|
});
|
|
|
|
// Community report (forum #10700, issue #2): columnSpan was said to expand
|
|
// the grid's column count and scramble the other cells' positions. Did not
|
|
// reproduce; kept as a regression pin.
|
|
test('columnSpan spans a cell without changing column count or other cells', (ctx) => {
|
|
const b = board(ctx);
|
|
b.resize(600, 400);
|
|
const grid = b.addGridLayout();
|
|
grid.addColumn('flex', 1);
|
|
grid.addColumn('flex', 1);
|
|
grid.addColumn('flex', 1);
|
|
grid.addRow('flex', 1);
|
|
grid.addRow('flex', 1);
|
|
|
|
const a = ctx.penpot.createRectangle();
|
|
const c = ctx.penpot.createRectangle();
|
|
const d = ctx.penpot.createRectangle();
|
|
grid.appendChild(a, 1, 1);
|
|
grid.appendChild(c, 1, 3);
|
|
grid.appendChild(d, 2, 2);
|
|
|
|
const cellA = a.layoutCell;
|
|
expect(cellA).toBeDefined();
|
|
if (cellA) {
|
|
cellA.columnSpan = 2;
|
|
}
|
|
|
|
expect(grid.columns.length).toBe(3);
|
|
expect(grid.rows.length).toBe(2);
|
|
if (cellA) {
|
|
expect(cellA.row).toBeCloseTo(1, 0);
|
|
expect(cellA.column).toBeCloseTo(1, 0);
|
|
expect(cellA.columnSpan).toBeCloseTo(2, 0);
|
|
}
|
|
const cellC = c.layoutCell;
|
|
const cellD = d.layoutCell;
|
|
expect(cellC).toBeDefined();
|
|
expect(cellD).toBeDefined();
|
|
if (cellC && cellD) {
|
|
expect(cellC.row).toBeCloseTo(1, 0);
|
|
expect(cellC.column).toBeCloseTo(3, 0);
|
|
expect(cellD.row).toBeCloseTo(2, 0);
|
|
expect(cellD.column).toBeCloseTo(2, 0);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Sizing', () => {
|
|
// Community report (forum #10700, feature request C): resize on a board
|
|
// whose layout hugs content (auto sizing) must behave like the UI — switch
|
|
// the sizing to fixed and apply the requested dimensions. This once snapped
|
|
// back to the hugged content (flex) or left the sizing 'auto' (grid). Fixed;
|
|
// kept as a regression pin.
|
|
test('resize on an auto-sized flex board switches sizing to fixed', (ctx) => {
|
|
const b = board(ctx);
|
|
b.resize(300, 200);
|
|
const flex = b.addFlexLayout();
|
|
flex.horizontalSizing = 'auto';
|
|
flex.verticalSizing = 'auto';
|
|
const child = ctx.penpot.createRectangle();
|
|
child.resize(50, 50);
|
|
b.appendChild(child);
|
|
|
|
b.resize(500, 400);
|
|
expect(b.width).toBeCloseTo(500, 0);
|
|
expect(b.height).toBeCloseTo(400, 0);
|
|
expect(flex.horizontalSizing).toBe('fix');
|
|
expect(flex.verticalSizing).toBe('fix');
|
|
});
|
|
|
|
test('resize on an auto-sized grid board switches sizing to fixed', (ctx) => {
|
|
const b = board(ctx);
|
|
b.resize(300, 200);
|
|
const grid = b.addGridLayout();
|
|
grid.addColumn('auto');
|
|
grid.addRow('auto');
|
|
grid.horizontalSizing = 'auto';
|
|
grid.verticalSizing = 'auto';
|
|
const child = ctx.penpot.createRectangle();
|
|
child.resize(50, 50);
|
|
grid.appendChild(child, 1, 1);
|
|
|
|
b.resize(500, 400);
|
|
expect(b.width).toBeCloseTo(500, 0);
|
|
expect(b.height).toBeCloseTo(400, 0);
|
|
expect(grid.horizontalSizing).toBe('fix');
|
|
expect(grid.verticalSizing).toBe('fix');
|
|
});
|
|
});
|
|
|
|
describe('Switching type', () => {
|
|
// addFlexLayout/addGridLayout do not reject a board that already has a
|
|
// layout; they create the requested layout (switching the board's type).
|
|
// These pin that behaviour.
|
|
test('adding a grid layout to a board that already has a flex layout switches to grid', (ctx) => {
|
|
const b = board(ctx);
|
|
b.addFlexLayout();
|
|
expect(() => b.addGridLayout()).not.toThrow();
|
|
expect(b.grid).toBeDefined();
|
|
});
|
|
|
|
test('adding a flex layout to a board that already has a grid layout switches to flex', (ctx) => {
|
|
const b = board(ctx);
|
|
b.addGridLayout();
|
|
expect(() => b.addFlexLayout()).not.toThrow();
|
|
expect(b.flex).toBeDefined();
|
|
});
|
|
});
|
|
});
|