import { afterEach, it, expect, describe, vitest } from "vitest";
import { render, cleanup } from "@testing-library/react";
import { VList } from "./VList";
import { Profiler, ReactElement, forwardRef, useEffect, useState } from "react";
import { CustomItemComponentProps } from "./types";
import { setupJsDomEnv } from "../../scripts/spec";
const ITEM_HEIGHT = 50;
const ITEM_WIDTH = 100;
const VIEWPORT_HEIGHT = ITEM_HEIGHT * 10;
setupJsDomEnv({
itemWidth: ITEM_WIDTH,
itemHeight: ITEM_HEIGHT,
viewportHeight: VIEWPORT_HEIGHT,
});
afterEach(cleanup);
it("should pass attributes to element", () => {
const { asFragment } = render(
0
);
expect(asFragment()).toMatchSnapshot();
});
it("should pass index to items", () => {
const Item = forwardRef(
({ index, ...rest }, ref) => {
return (
);
}
);
const { asFragment } = render(
0
1
2
3
4
);
expect(asFragment()).toMatchSnapshot();
});
it("should render with render prop", () => {
const items = Array.from({ length: 1000 }).map((_, i) => ({
id: i,
label: "This is " + i,
}));
const { asFragment } = render(
{(i) => {
const item = items[i]!;
return {item.label}
;
}}
);
expect(asFragment()).toMatchSnapshot();
});
describe("vertical", () => {
it("should render 0 children", () => {
const { asFragment } = render({[]});
expect(asFragment()).toMatchSnapshot();
});
it("should render 1 children", () => {
const { asFragment } = render(
0
);
expect(asFragment()).toMatchSnapshot();
});
it("should render 5 children", () => {
const { asFragment } = render(
0
1
2
3
4
);
expect(asFragment()).toMatchSnapshot();
});
it("should render 100 children", () => {
const { asFragment } = render(
{Array.from({ length: 100 }).map((_, i) => (
{i}
))}
);
expect(asFragment()).toMatchSnapshot();
});
it("should render 1000 children", () => {
const { asFragment } = render(
{Array.from({ length: 1000 }).map((_, i) => (
{i}
))}
);
expect(asFragment()).toMatchSnapshot();
});
it("should render 10000 children", () => {
const { asFragment } = render(
{Array.from({ length: 10000 }).map((_, i) => (
{i}
))}
);
expect(asFragment()).toMatchSnapshot();
});
it("should render non elements", () => {
const { asFragment } = render(
string
{true}
{false}
{null}
{undefined}
{123}
);
expect(asFragment()).toMatchSnapshot();
});
it("should render fragments", () => {
const { asFragment } = render(
<>
fragment
fragment
fragment
>
<>
fragment
>
);
expect(asFragment()).toMatchSnapshot();
});
it("should render component", () => {
const Comp = ({ children }: { children: React.ReactNode }) => (
{children}
);
const { asFragment } = render(
component
component
component
);
expect(asFragment()).toMatchSnapshot();
});
it("should render with given width / height", () => {
const { asFragment } = render(
0
1
2
3
4
);
expect(asFragment()).toMatchSnapshot();
});
});
describe("horizontal", () => {
it("should render 1 children", () => {
const { asFragment } = render(
0
);
expect(asFragment()).toMatchSnapshot();
});
it("should render 5 children", () => {
const { asFragment } = render(
0
1
2
3
4
);
expect(asFragment()).toMatchSnapshot();
});
it("should render 100 children", () => {
const { asFragment } = render(
{Array.from({ length: 100 }).map((_, i) => (
{i}
))}
);
expect(asFragment()).toMatchSnapshot();
});
it("should render 1000 children", () => {
const { asFragment } = render(
{Array.from({ length: 1000 }).map((_, i) => (
{i}
))}
);
expect(asFragment()).toMatchSnapshot();
});
it("should render 10000 children", () => {
const { asFragment } = render(
{Array.from({ length: 10000 }).map((_, i) => (
{i}
))}
);
expect(asFragment()).toMatchSnapshot();
});
it("should render non elements", () => {
const { asFragment } = render(
string
{true}
{false}
{null}
{undefined}
{123}
);
expect(asFragment()).toMatchSnapshot();
});
it("should render fragments", () => {
const { asFragment } = render(
<>
fragment
fragment
fragment
>
<>
fragment
>
);
expect(asFragment()).toMatchSnapshot();
});
it("should render component", () => {
const Comp = ({ children }: { children: React.ReactNode }) => (
{children}
);
const { asFragment } = render(
component
component
component
);
expect(asFragment()).toMatchSnapshot();
});
it("should render with given width / height", () => {
const { asFragment } = render(
0
1
2
3
4
);
expect(asFragment()).toMatchSnapshot();
});
});
describe("reverse", () => {
it("should render many items", () => {
const { asFragment } = render(
{Array.from({ length: 100 }).map((_, i) => (
{i}
))}
);
expect(asFragment()).toMatchSnapshot();
});
});
describe("render count", () => {
it("should render on mount", () => {
const rootFn = vitest.fn();
const itemCount = 4;
const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());
render(
{Array.from({ length: itemCount }, (_, i) => {
const key = `item-${i}`;
return (
{i}
);
})}
);
expect(rootFn).toBeCalledTimes(2);
itemFns.forEach((itemFn) => {
expect(itemFn).toHaveBeenCalledTimes(1);
});
});
it("should render on mount many items", () => {
const rootFn = vitest.fn();
const itemCount = 100;
const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());
render(
{Array.from({ length: itemCount }, (_, i) => {
const key = `item-${i}`;
return (
{i}
);
})}
);
expect(rootFn).toBeCalledTimes(3);
itemFns.forEach((itemFn) => {
expect(itemFn.mock.calls.length).toBeLessThanOrEqual(1);
});
});
it("should render on length change", () => {
let ready = false;
const wrap = (f: (...args: any[]) => void) => {
return (...args: any[]) => {
if (!ready) return;
f(...args);
};
};
const rootFn = vitest.fn();
const itemCount = 4;
const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());
const Mounter = ({
children,
count: initialCount,
}: {
children: (count: number) => ReactElement;
count: number;
}): ReactElement => {
const [count, setCount] = useState(initialCount);
useEffect(() => {
ready = true;
setCount((prev) => {
return prev - 1;
});
}, []);
return children(count);
};
render(
{(count) => (
{Array.from({ length: count }, (_, i) => {
const key = `item-${i}`;
return (
{i}
);
})}
)}
);
expect(rootFn).toBeCalledTimes(2);
itemFns.forEach((itemFn, i) => {
expect(itemFn).toBeCalledTimes(i === itemFns.length - 1 ? 0 : 1);
});
});
it("should render on length change many items", () => {
let ready = false;
const wrap = (f: (...args: any[]) => void) => {
return (...args: any[]) => {
if (!ready) return;
f(...args);
};
};
const rootFn = vitest.fn();
const itemCount = 100;
const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());
const Mounter = ({
children,
count: initialCount,
}: {
children: (count: number) => ReactElement;
count: number;
}): ReactElement => {
const [count, setCount] = useState(initialCount);
useEffect(() => {
ready = true;
setCount((prev) => {
return prev - 1;
});
}, []);
return children(count);
};
render(
{(count) => (
{Array.from({ length: count }, (_, i) => {
const key = `item-${i}`;
return (
{i}
);
})}
)}
);
expect(rootFn).toBeCalledTimes(3);
itemFns.forEach((itemFn) => {
expect(itemFn.mock.calls.length).toBeLessThanOrEqual(2); // TODO: should be 1
});
});
});