Skip to content

Commit

Permalink
WindowScroller imperative scrolling (#540)
Browse files Browse the repository at this point in the history
* WindowScroller imperative scrolling

* Update src/react/WindowVirtualizer.tsx

Co-authored-by: inokawa <48897392+inokawa@users.noreply.github.com>

* fixed ts error: getcurrentwindow

* removing unnecessary startmargin

* scrolltoindex end adjustment for window scrollbar

* windowvirtualizer scrolltoindex e2e

* removing windowscroller scrollto and scrollby, not needed

* Update utils.ts

* Catch up main

* Fix errors

* fixed: windowscroller scrolltoindex getstartspacersize not needed

* updated scrolltoindex tests to include paadding around virtualizer

* Remove methods not existed in WindowVirtualizerHandle

* Some refactor

---------

Co-authored-by: inokawa <48897392+inokawa@users.noreply.github.com>
  • Loading branch information
herschelrs and inokawa authored Nov 21, 2024
1 parent 266ce47 commit 3f0881e
Show file tree
Hide file tree
Showing 6 changed files with 581 additions and 55 deletions.
27 changes: 2 additions & 25 deletions e2e/VList.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect, ElementHandle, Locator } from "@playwright/test";
import { test, expect } from "@playwright/test";
import {
storyUrl,
getFirstItem,
Expand All @@ -20,32 +20,9 @@ import {
getScrollable,
clearTimer,
scrollTo,
listenScrollCount,
} from "./utils";

const listenScrollCount = (
component: Locator,
timeout = 2000
): Promise<number> => {
return component.evaluate((c, t) => {
let timer: null | ReturnType<typeof setTimeout> = null;
let called = 0;

return new Promise<number>((resolve) => {
const cb = () => {
called++;
if (timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(() => {
c.removeEventListener("scroll", cb);
resolve(called);
}, t);
};
c.addEventListener("scroll", cb);
});
}, timeout);
};

test.describe("smoke", () => {
test("vertically scrollable", async ({ page }) => {
await page.goto(storyUrl("basics-vlist--default"));
Expand Down
274 changes: 273 additions & 1 deletion e2e/WindowVirtualizer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect } from "@playwright/test";
import { test, expect, ElementHandle } from "@playwright/test";
import {
storyUrl,
getFirstItem,
Expand All @@ -15,6 +15,8 @@ import {
windowScrollBy,
getWindowFirstItem,
getWindowLastItem,
clearInput,
listenScrollCount,
} from "./utils";

test.describe("smoke", () => {
Expand Down Expand Up @@ -427,3 +429,273 @@ test.describe("RTL", () => {
expect(text).not.toContain("949");
});
});

test.describe("check if scrollToIndex works", () => {
test.beforeEach(async ({ page }) => {
await page.goto(storyUrl("basics-windowvirtualizer--scroll-to"));
});

test.describe("align start", () => {
test("mid", async ({ page }) => {
const component = await getVirtualizer(page);

// check if start is displayed
expect((await getFirstItem(component)).text).toEqual("0");

const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);

await clearInput(input);
await input.fill("700");
await button.click();

await (await component.elementHandle())!.waitForElementState("stable");

// Check if scrolled precisely
const firstItem = await getWindowFirstItem(page, { x: 102 });
expect(firstItem.text).toEqual("700");
expect(firstItem.top).toEqual(0);

// Check if unnecessary items are not rendered
expect(await component.innerText()).not.toContain("650");
expect(await component.innerText()).not.toContain("750");
});

test("start", async ({ page }) => {
const component = await getVirtualizer(page);

// check if start is displayed
expect((await getFirstItem(component)).text).toEqual("0");

const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);

await clearInput(input);
await input.fill("500");
await button.click();

await (await component.elementHandle())!.waitForElementState("stable");

expect(await component.innerText()).toContain("500");

await clearInput(input);
await input.fill("0");
await button.click();

await (await component.elementHandle())!.waitForElementState("stable");

// Check if scrolled precisely
const firstItem = await getWindowFirstItem(page, { x: 102 });
expect(firstItem.text).toEqual("0");
expect(firstItem.top).toEqual(0);

// Check if unnecessary items are not rendered
expect(await component.innerText()).not.toContain("50\n");
});

test("end", async ({ page }) => {
const component = await getVirtualizer(page);

// check if start is displayed
expect((await getFirstItem(component)).text).toEqual("0");

const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);

await clearInput(input);
await input.fill("999");
await button.click();

await (await component.elementHandle())!.waitForElementState("stable");

// Check if scrolled precisely
const lastItem = await getWindowLastItem(page, { x: 102, y: 102 });
expect(lastItem.text).toEqual("999");
expectInRange(lastItem.bottom, { min: -101.9, max: 100 });

// Check if unnecessary items are not rendered
expect(await component.innerText()).not.toContain("949");
});

test("mid smooth", async ({ page, browserName }) => {
const component = await getVirtualizer(page);

const window = await page.evaluateHandle(() => window);

// check if start is displayed
expect((await getFirstItem(component)).text).toEqual("0");

await page.getByRole("checkbox", { name: "smooth" }).click();

const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);

const scrollListener = listenScrollCount(window);

await clearInput(input);
await input.fill("700");
await button.click();

await page.waitForTimeout(500);

const called = await scrollListener;

// Check if this is smooth scrolling
expect(called).toBeGreaterThanOrEqual(
// TODO find better way to check in webkit
browserName === "webkit" ? 2 : 10
);

// Check if scrolled precisely
const firstItem = await getWindowFirstItem(page, { x: 102 });
expect(firstItem.text).toEqual("700");
expectInRange(firstItem.top, { min: 0, max: 1 });

// Check if unnecessary items are not rendered
expect(await component.innerText()).not.toContain("650");
expect(await component.innerText()).not.toContain("750");
});
});

test.describe("align end", () => {
test.beforeEach(async ({ page }) => {
await page.getByRole("radio", { name: "end" }).click();
});

test("mid", async ({ page }) => {
const component = await getVirtualizer(page);

// check if start is displayed
expect((await getFirstItem(component)).text).toEqual("0");

const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);

await clearInput(input);
await input.fill("700");
await button.click();

await (await component.elementHandle())!.waitForElementState("stable");

// Check if scrolled precisely
const lastItem = await getWindowLastItem(page, { x: 102 });
expect(lastItem.text).toEqual("700");
expectInRange(lastItem.bottom, { min: 0, max: 1 });

// Check if unnecessary items are not rendered
expect(await component.innerText()).not.toContain("650");
expect(await component.innerText()).not.toContain("750");
});

test("start", async ({ page }) => {
const component = await getVirtualizer(page);

// check if start is displayed
expect((await getFirstItem(component)).text).toEqual("0");

const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);

await clearInput(input);
await input.fill("500");
await button.click();

await (await component.elementHandle())!.waitForElementState("stable");

expect(await component.innerText()).toContain("500");

await clearInput(input);
await input.fill("0");
await button.click();

await (await component.elementHandle())!.waitForElementState("stable");

// Check if scrolled precisely
const firstItem = await getFirstItem(component);
expect(firstItem.text).toEqual("0");
expect(firstItem.top).toEqual(0);

// Check if unnecessary items are not rendered
expect(await component.innerText()).not.toContain("50\n");
});

test("end", async ({ page }) => {
const component = await getVirtualizer(page);

// check if start is displayed
expect((await getFirstItem(component)).text).toEqual("0");

const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);

await clearInput(input);
await input.fill("999");
await button.click();

await (await component.elementHandle())!.waitForElementState("stable");

// Check if scrolled precisely
const lastItem = await getWindowLastItem(page, { x: 102 });
expect(lastItem.text).toEqual("999");
expectInRange(lastItem.bottom, { min: 0, max: 1 });

// Check if unnecessary items are not rendered
expect(await component.innerText()).not.toContain("949");
});

test("mid smooth", async ({ page, browserName }) => {
const component = await getVirtualizer(page);

const window = await page.evaluateHandle(() => window);

// check if start is displayed
expect((await getFirstItem(component)).text).toEqual("0");

await page.getByRole("checkbox", { name: "smooth" }).click();

const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);

const scrollListener = listenScrollCount(window);

await clearInput(input);
await input.fill("700");
await button.click();

await page.waitForTimeout(500);

const called = await scrollListener;

// Check if this is smooth scrolling
expect(called).toBeGreaterThanOrEqual(
// TODO find better way to check in webkit
browserName === "webkit" ? 2 : 10
);

// Check if scrolled precisely
const lastItem = await getWindowLastItem(page, { x: 102 });
expect(lastItem.text).toEqual("700");
expectInRange(lastItem.bottom, { min: 0, max: 1 });

// Check if unnecessary items are not rendered
expect(await component.innerText()).not.toContain("650");
expect(await component.innerText()).not.toContain("750");
});
});
});
24 changes: 24 additions & 0 deletions e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,27 @@ export const scrollWithTouch = (
target
);
};

export const listenScrollCount = (
component: Locator,
timeout = 2000
): Promise<number> => {
return component.evaluate((c, t) => {
let timer: null | ReturnType<typeof setTimeout> = null;
let called = 0;

return new Promise<number>((resolve) => {
const cb = () => {
called++;
if (timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(() => {
c.removeEventListener("scroll", cb);
resolve(called);
}, t);
};
c.addEventListener("scroll", cb);
});
}, timeout);
};
Loading

0 comments on commit 3f0881e

Please sign in to comment.