Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
92 changes: 92 additions & 0 deletions src/__tests__/GridLayoutManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,98 @@ describe("GridLayoutManager", () => {
});
});

describe("Separator behavior", () => {
it("should mark last row items to skip separators in 2x2 grid", () => {
const manager = createPopulatedLayoutManager(
LayoutManagerType.GRID,
4,
defaultParams
);
const layouts = getAllLayouts(manager);

// First row items should not skip separators
expect(layouts[0].skipSeparator).toBeFalsy();
expect(layouts[1].skipSeparator).toBeFalsy();

// Last row items should skip separators
expect(layouts[2].skipSeparator).toBe(true);
expect(layouts[3].skipSeparator).toBe(true);
});

it("should mark last row items to skip separators in 3x3 grid", () => {
const manager = createPopulatedLayoutManager(LayoutManagerType.GRID, 6, {
...defaultParams,
maxColumns: 3,
});
const layouts = getAllLayouts(manager);

// First row items should not skip separators
expect(layouts[0].skipSeparator).toBeFalsy();
expect(layouts[1].skipSeparator).toBeFalsy();
expect(layouts[2].skipSeparator).toBeFalsy();

// Last row items should skip separators
expect(layouts[3].skipSeparator).toBe(true);
expect(layouts[4].skipSeparator).toBe(true);
expect(layouts[5].skipSeparator).toBe(true);
});

it("should mark last row items to skip separators with uneven rows", () => {
const manager = createPopulatedLayoutManager(
LayoutManagerType.GRID,
5,
defaultParams
);
const layouts = getAllLayouts(manager);

// First two rows should not skip separators
expect(layouts[0].skipSeparator).toBeFalsy();
expect(layouts[1].skipSeparator).toBeFalsy();
expect(layouts[2].skipSeparator).toBeFalsy();
expect(layouts[3].skipSeparator).toBeFalsy();

// Last row (single item) should skip separator
expect(layouts[4].skipSeparator).toBe(true);
});

it("should handle single item grid", () => {
const manager = createPopulatedLayoutManager(
LayoutManagerType.GRID,
1,
defaultParams
);
const layouts = getAllLayouts(manager);

// Single item should skip separator
expect(layouts[0].skipSeparator).toBe(true);
});

it("should update skipSeparator when items are added dynamically", () => {
const manager = createPopulatedLayoutManager(
LayoutManagerType.GRID,
2,
defaultParams
);
let layouts = getAllLayouts(manager);

// Initially, both items are in last row
expect(layouts[0].skipSeparator).toBe(true);
expect(layouts[1].skipSeparator).toBe(true);

// Add two more items to complete the grid
manager.modifyLayout([], 4);
layouts = getAllLayouts(manager);

// First row should not skip separators anymore
expect(layouts[0].skipSeparator).toBeFalsy();
expect(layouts[1].skipSeparator).toBeFalsy();

// New last row should skip separators
expect(layouts[2].skipSeparator).toBe(true);
expect(layouts[3].skipSeparator).toBe(true);
});
});

describe("Layout recalculations", () => {
it("should adjust layout when window size changes", () => {
const manager = createPopulatedLayoutManager(
Expand Down
253 changes: 253 additions & 0 deletions src/__tests__/ViewHolder.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import React from "react";
import { Text, View } from "react-native";
import "@quilted/react-testing/matchers";
import { render } from "@quilted/react-testing";

import { ViewHolder } from "../recyclerview/ViewHolder";
import type { RVLayout } from "../recyclerview/layout-managers/LayoutManager";
import type { ViewHolderProps } from "../recyclerview/ViewHolder";

// Mock CompatView component
jest.mock("../recyclerview/components/CompatView", () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ReactLib = require("react");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { View: ViewComponent } = require("react-native");

const CompatView = ReactLib.forwardRef((props: any, ref: any) => {
const { children, ...otherProps } = props;
return ReactLib.createElement(
ViewComponent,
{ ref, ...otherProps },
children
);
});

CompatView.displayName = "CompatView";

return { CompatView };
});

describe("ViewHolder", () => {
const mockRefHolder = new Map();
const mockRenderItem = jest.fn(({ item }) => <Text>{item.text}</Text>);
const mockSeparatorComponent = jest.fn(({ leadingItem, trailingItem }) => (
<View>
<Text>
Separator between {leadingItem.text} and {trailingItem.text}
</Text>
</View>
));

const defaultLayout: RVLayout = {
x: 0,
y: 0,
width: 100,
height: 50,
isWidthMeasured: true,
isHeightMeasured: true,
};

const defaultProps: ViewHolderProps<{ text: string }> = {
index: 0,
layout: defaultLayout,
refHolder: mockRefHolder,
extraData: null,
target: "Cell",
item: { text: "Item 1" },
trailingItem: { text: "Item 2" },
renderItem: mockRenderItem,
ItemSeparatorComponent: mockSeparatorComponent,
horizontal: false,
};

beforeEach(() => {
jest.clearAllMocks();
mockRefHolder.clear();
});

describe("Separator rendering", () => {
it("should render separator when skipSeparator is false", () => {
const result = render(
<ViewHolder
{...defaultProps}
layout={{ ...defaultLayout, skipSeparator: false }}
/>
);

expect(result).toContainReactComponent(Text, {
children: ["Separator between ", "Item 1", " and ", "Item 2"],
});
expect(mockSeparatorComponent).toHaveBeenCalledWith(
{
leadingItem: { text: "Item 1" },
trailingItem: { text: "Item 2" },
},
{}
);
});

it("should render separator when skipSeparator is undefined", () => {
const result = render(
<ViewHolder
{...defaultProps}
layout={{ ...defaultLayout, skipSeparator: undefined }}
/>
);

expect(result).toContainReactComponent(Text, {
children: ["Separator between ", "Item 1", " and ", "Item 2"],
});
expect(mockSeparatorComponent).toHaveBeenCalledWith(
{
leadingItem: { text: "Item 1" },
trailingItem: { text: "Item 2" },
},
{}
);
});

it("should not render separator when skipSeparator is true", () => {
const result = render(
<ViewHolder
{...defaultProps}
layout={{ ...defaultLayout, skipSeparator: true }}
/>
);

expect(result).not.toContainReactComponent(Text, {
children: ["Separator between ", "Item 1", " and ", "Item 2"],
});
expect(mockSeparatorComponent).not.toHaveBeenCalled();
});

it("should not render separator when trailingItem is undefined", () => {
const result = render(
<ViewHolder
{...defaultProps}
trailingItem={undefined}
layout={{ ...defaultLayout, skipSeparator: false }}
/>
);

expect(result).not.toContainReactComponent(Text, {
children: ["Separator between ", "Item 1", " and ", "Item 2"],
});
expect(mockSeparatorComponent).not.toHaveBeenCalled();
});

it("should not render separator when ItemSeparatorComponent is undefined", () => {
const result = render(
<ViewHolder
{...defaultProps}
ItemSeparatorComponent={undefined}
layout={{ ...defaultLayout, skipSeparator: false }}
/>
);

expect(result).not.toContainReactComponent(Text, {
children: ["Separator between ", "Item 1", " and ", "Item 2"],
});
expect(mockSeparatorComponent).not.toHaveBeenCalled();
});
});

describe("Memoization behavior", () => {
it("should re-render when skipSeparator changes from false to true", () => {
const result = render(
<ViewHolder
{...defaultProps}
layout={{ ...defaultLayout, skipSeparator: false }}
/>
);

// Initially separator should be rendered
expect(result).toContainReactComponent(Text, {
children: ["Separator between ", "Item 1", " and ", "Item 2"],
});

// Change skipSeparator to true
result.setProps({
layout: { ...defaultLayout, skipSeparator: true },
});

// Separator should no longer be rendered
expect(result).not.toContainReactComponent(Text, {
children: ["Separator between ", "Item 1", " and ", "Item 2"],
});
});

it("should re-render when skipSeparator changes from true to false", () => {
const result = render(
<ViewHolder
{...defaultProps}
layout={{ ...defaultLayout, skipSeparator: true }}
/>
);

// Initially separator should not be rendered
expect(result).not.toContainReactComponent(Text, {
children: ["Separator between ", "Item 1", " and ", "Item 2"],
});

// Change skipSeparator to false
result.setProps({
layout: { ...defaultLayout, skipSeparator: false },
});

// Separator should now be rendered
expect(result).toContainReactComponent(Text, {
children: ["Separator between ", "Item 1", " and ", "Item 2"],
});
});
});

describe("Item rendering", () => {
it("should always render the item content regardless of skipSeparator", () => {
const result = render(
<ViewHolder
{...defaultProps}
layout={{ ...defaultLayout, skipSeparator: true }}
/>
);

expect(result).toContainReactComponent(Text, { children: "Item 1" });
expect(mockRenderItem).toHaveBeenCalledWith({
item: { text: "Item 1" },
index: 0,
extraData: null,
target: "Cell",
});

// Re-render with skipSeparator false
result.setProps({
layout: { ...defaultLayout, skipSeparator: false },
});

expect(result).toContainReactComponent(Text, { children: "Item 1" });
});
});

describe("Layout styles", () => {
it("should apply layout styles correctly regardless of skipSeparator", () => {
const customLayout: RVLayout = {
x: 10,
y: 20,
width: 200,
height: 100,
skipSeparator: true,
enforcedWidth: true,
enforcedHeight: true,
};

const result = render(
<ViewHolder {...defaultProps} layout={customLayout} />
);

// Verify the item is rendered with correct content
expect(result).toContainReactComponent(Text, { children: "Item 1" });
// Note: Layout style verification would require more complex testing setup
// as @quilted/react-testing doesn't provide direct style inspection
});
});
});
Loading