@shopify/flash-list
Version:
FlashList is a more performant FlatList replacement
203 lines (179 loc) • 7.25 kB
text/typescript
import { RVLayoutManager } from "../recyclerview/layout-managers/LayoutManager";
import { RVMasonryLayoutManagerImpl } from "../recyclerview/layout-managers/MasonryLayoutManager";
import {
getAllLayouts,
LayoutManagerType,
createLayoutParams,
createLayoutManager,
createMockLayoutInfo,
} from "./helpers/createLayoutManager";
describe("MasonryLayoutManager", () => {
const windowSize = { width: 400, height: 900 };
const defaultParams = {
windowSize,
maxColumns: 2,
optimizeItemArrangement: true,
};
// Helper to get column heights
const getColumnHeights = (manager: RVLayoutManager): number[] => {
return (manager as RVMasonryLayoutManagerImpl)["columnHeights"];
};
describe("Vertical Masonry Layout", () => {
it("should distribute items into columns based on height", () => {
const manager = createLayoutManager(
LayoutManagerType.MASONRY,
defaultParams
);
const layoutInfos = [
createMockLayoutInfo(0, 200, 100), // Col 0
createMockLayoutInfo(1, 200, 150), // Col 1
createMockLayoutInfo(2, 200, 120), // Col 0 (shorter)
createMockLayoutInfo(3, 200, 80), // Col 1 (shorter)
createMockLayoutInfo(4, 200, 200), // Col 0 (shorter)
];
manager.modifyLayout(layoutInfos, 5);
const layouts = getAllLayouts(manager);
expect(layouts[0].x).toBe(0);
expect(layouts[0].y).toBe(0);
expect(layouts[1].x).toBe(200); // Second column
expect(layouts[1].y).toBe(0);
expect(layouts[2].x).toBe(0); // Back to first column
expect(layouts[2].y).toBe(100); // Below item 0
expect(layouts[3].x).toBe(200); // Still first column
expect(layouts[3].y).toBe(150); // Below item 2 (100 + 120)
expect(layouts[4].x).toBe(0); // Second column
expect(layouts[4].y).toBe(220); // Below item 1
});
it("should respect maxColumns configuration", () => {
const manager = createLayoutManager(LayoutManagerType.MASONRY, {
...defaultParams,
maxColumns: 3,
});
const layoutInfos = [
createMockLayoutInfo(0, 133, 100), // Col 0
createMockLayoutInfo(1, 133, 150), // Col 1
createMockLayoutInfo(2, 133, 120), // Col 2
createMockLayoutInfo(3, 133, 80), // Col 0
];
manager.modifyLayout(layoutInfos, 4);
const layouts = getAllLayouts(manager);
const colWidth = windowSize.width / 3;
expect(layouts[0].x).toBeCloseTo(0);
expect(layouts[1].x).toBeCloseTo(colWidth);
expect(layouts[2].x).toBeCloseTo(colWidth * 2);
expect(layouts[3].x).toBeCloseTo(0); // Placed in the shortest column (Col 0)
expect(layouts[3].y).toBeCloseTo(100); // Below item 0
});
it("should calculate total layout size correctly", () => {
const manager = createLayoutManager(
LayoutManagerType.MASONRY,
defaultParams
);
const layoutInfos = [
createMockLayoutInfo(0, 200, 100), // Col 0
createMockLayoutInfo(1, 200, 150), // Col 1
createMockLayoutInfo(2, 200, 120), // Col 0
];
manager.modifyLayout(layoutInfos, 3);
const layoutSize = manager.getLayoutSize();
expect(layoutSize.width).toBe(400);
// Height is the tallest column height
const heights = getColumnHeights(manager);
expect(layoutSize.height).toBeCloseTo(Math.max(...heights)); // Max of [220, 150]
expect(layoutSize.height).toBeCloseTo(220);
});
});
describe("Layout Modifications", () => {
it("should update layout when items are added", () => {
const manager = createLayoutManager(
LayoutManagerType.MASONRY,
defaultParams
);
const initialInfos = [
createMockLayoutInfo(0, 200, 100), // Col 0 H=100
createMockLayoutInfo(1, 200, 150), // Col 1 H=150
];
manager.modifyLayout(initialInfos, 2);
expect(getAllLayouts(manager).length).toBe(2);
expect(getColumnHeights(manager)).toEqual([100, 150]);
// Add item, should go to Col 0
const newLayoutInfos = [createMockLayoutInfo(2, 200, 120)];
manager.modifyLayout(newLayoutInfos, 3);
const layouts = getAllLayouts(manager);
expect(layouts.length).toBe(3);
expect(layouts[2].x).toBe(0); // Col 0
expect(layouts[2].y).toBe(100); // Below item 0
expect(getColumnHeights(manager)).toEqual([220, 150]); // 100+120, 150
});
it("should handle removing items (requires full recalculation)", () => {
const manager = createLayoutManager(
LayoutManagerType.MASONRY,
defaultParams
);
const initialInfos = [
createMockLayoutInfo(0, 200, 100), // Col 0 H=100
createMockLayoutInfo(1, 200, 150), // Col 1 H=150
createMockLayoutInfo(2, 200, 120), // Col 0 H=220
];
manager.modifyLayout(initialInfos, 3);
expect(getColumnHeights(manager)).toEqual([220, 150]);
// Remove item 2 (from Col 0) - Masonry usually recalculates fully
// We simulate this by passing the remaining items
const remainingInfos = [
createMockLayoutInfo(0, 200, 100),
createMockLayoutInfo(1, 200, 150),
];
manager.modifyLayout(remainingInfos, 2);
const layouts = getAllLayouts(manager);
expect(layouts.length).toBe(2);
expect(getColumnHeights(manager)).toEqual([100, 150]); // Back to original state
});
it("should recalculate layout when window size changes", () => {
const manager = createLayoutManager(
LayoutManagerType.MASONRY,
defaultParams
);
const initialInfos = [
createMockLayoutInfo(0, 200, 100), // Col 0
createMockLayoutInfo(1, 200, 150), // Col 1
createMockLayoutInfo(2, 200, 120), // Col 0
];
manager.modifyLayout(initialInfos, 3);
const initialLayouts = getAllLayouts(manager);
expect(initialLayouts[1].x).toBe(200);
// Change window size and columns
manager.updateLayoutParams(
createLayoutParams({
...defaultParams,
maxColumns: 3,
windowSize: { width: 600, height: 900 },
})
);
// modifyLayout needs to be called again as dimensions depend on width
const updatedInfos = [
createMockLayoutInfo(0, 200, 100), // New width = 600/3 = 200
createMockLayoutInfo(1, 200, 150),
createMockLayoutInfo(2, 200, 120),
];
manager.modifyLayout(updatedInfos, 3);
const updatedLayouts = getAllLayouts(manager);
expect(updatedLayouts[0].width).toBe(200);
expect(updatedLayouts[1].x).toBe(200); // Col 1 starts at 200
expect(updatedLayouts[2].x).toBe(400); // Col 2 starts at 400
expect(getColumnHeights(manager)).toEqual([100, 150, 120]);
});
});
describe("Empty Layout", () => {
it("should return zero size for empty layout", () => {
const manager = createLayoutManager(
LayoutManagerType.MASONRY,
defaultParams
);
manager.modifyLayout([], 0);
const layoutSize = manager.getLayoutSize();
expect(layoutSize.width).toBe(0);
expect(layoutSize.height).toBe(0);
expect(getAllLayouts(manager).length).toBe(0);
});
});
});