Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
82 changes: 82 additions & 0 deletions e2e/virtualization.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,88 @@ test.describe('virtualization', () => {
expect(Math.abs(cellBox!.y - focusBox!.y)).toBeLessThan(2);
});

test('fully reveals the last row in million-row mode', async ({ page }) => {
const rowSize = 30;
const rowCount = 1_000_000;
const lastRow = rowCount - 1;

await page.setContent(`
<div style="width:520px; height:260px;">
<revo-grid style="display:block; width:100%; height:100%;"></revo-grid>
</div>
`);
await page.waitForSelector(SELECTORS.grid);
await page.evaluate(({ rowCount, rowSize }) => {
const grid = document.querySelector<HTMLRevoGridElement>('revo-grid');
if (!grid) {
throw new Error('Grid element was not created');
}
grid.columns = [{ prop: 'id', name: 'ID', size: 120 }];
grid.source = Array.from({ length: rowCount }, (_, index) => ({ id: index + 1 }));
grid.rowSize = rowSize;
}, { rowCount, rowSize });
await page.waitForChanges();
await expect.poll(() => mainDataRows(page).count()).toBeGreaterThan(0);

await callGridMethod(page, 'scrollToRow', lastRow);
await page.waitForChanges();

const viewportBox = await page.locator(`${SELECTORS.mainViewport} .vertical-inner`).boundingBox();
const lastCell = dataCell(page, lastRow, 0);
await expect(lastCell).toHaveText(String(rowCount));
const lastCellBox = await lastCell.boundingBox();
expect(viewportBox).not.toBeNull();
expect(lastCellBox).not.toBeNull();
expect(lastCellBox!.y).toBeGreaterThanOrEqual(viewportBox!.y);
expect(lastCellBox!.y + lastCellBox!.height).toBeLessThanOrEqual(
viewportBox!.y + viewportBox!.height,
);
});

test('keeps the last row aligned in normal scroll mode', async ({ page }) => {
const rowSize = 30;
const rowCount = 100;
const lastRow = rowCount - 1;
const columns = Array.from({ length: 100 }, (_, index) => ({
prop: `c${index}`,
name: `C ${index}`,
size: 120,
}));
const source = Array.from({ length: rowCount }, (_, rowIndex) => {
const row: Record<string, string> = {};
for (let colIndex = 0; colIndex < columns.length; colIndex++) {
row[`c${colIndex}`] = `${rowIndex}:${colIndex}`;
}
return row;
});

await mountGrid(page, {
columns,
source,
width: 520,
height: 260,
rowSize,
});

await callGridMethod(page, 'scrollToRow', lastRow);
await page.waitForChanges();

const viewportBox = await page.locator(`${SELECTORS.mainViewport} .vertical-inner`).boundingBox();
const lastCell = dataCell(page, lastRow, 0);
await expect(lastCell).toHaveText(`${lastRow}:0`);
const lastCellBox = await lastCell.boundingBox();
expect(viewportBox).not.toBeNull();
expect(lastCellBox).not.toBeNull();
expect(lastCellBox!.y).toBeGreaterThanOrEqual(viewportBox!.y);
expect(lastCellBox!.y + lastCellBox!.height).toBeLessThanOrEqual(
viewportBox!.y + viewportBox!.height,
);
const renderedRowIndexes = await mainDataRows(page).evaluateAll(rows =>
rows.map(row => Number(row.getAttribute('data-rgrow'))),
);
expect(Math.max(...renderedRowIndexes)).toBe(lastRow);
});

test('maps native physical scroll and wheel deltas to logical vertical coordinates', async ({ page }) => {
const rowSize = 30;
const rowCount = 1_200_000;
Expand Down
8 changes: 3 additions & 5 deletions src/services/dimension.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
DimensionStoreCollection,
gatherTrimmedItems,
Trimmed,
clampViewportCoordinate,
} from '@store';
import type {
DimensionCols,
Expand Down Expand Up @@ -221,10 +220,9 @@ export default class DimensionProvider {
clientSize,
virtualSize: viewportSize,
});
const renderCoordinate = clampViewportCoordinate(
coordinate,
dimension,
viewportSize,
const renderCoordinate = Math.min(
Math.max(0, coordinate),
scrollDimension.logicalScrollSize,
);
const renderOffset =
clientSize && viewportSize
Expand Down
36 changes: 36 additions & 0 deletions test/scroll.dimension.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,42 @@ describe('browser-limit-aware scroll dimensions', () => {
expect(rowDimension.store.get('renderOffset')).toBe(renderOffset);
});

it('computes render offset from the real logical scroll bottom', () => {
const viewports = new ViewportProvider();
const dimensions = new DimensionProvider(viewports, {
realSizeChanged: () => undefined,
});
const rowDimension = dimensions.stores.rgRow;
const contentSize = 60_000_000;
const clientSize = 600;
rowDimension.setStore({
count: 2_000_000,
originItemSize: 30,
realSize: contentSize,
});
viewports.stores.rgRow.setViewport({
clientSize,
virtualSize: clientSize,
realCount: 2_000_000,
});
const scrollDimension = getScrollDimension({
contentSize,
clientSize,
virtualSize: clientSize,
});

dimensions.setViewPortCoordinate({
type: 'rgRow',
coordinate: scrollDimension.logicalScrollSize,
});

const renderOffset = viewports.stores.rgRow.store.get('renderOffset');
expect(renderOffset).toBe(
scrollDimension.getRenderOffset(scrollDimension.logicalScrollSize),
);
expect(rowDimension.store.get('renderOffset')).toBe(renderOffset);
});

it('recomputes render offset when viewport size changes', () => {
const firstRenderOffset = getScrollDimension({
contentSize: 60_000_000,
Expand Down
32 changes: 31 additions & 1 deletion test/viewport.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { type VirtualPositionItem, type ViewportStateItems } from '../src';
import { getFirstItem, getItems, getLastItem, recombineByOffset } from '../src/store/vp/viewport.helpers';
import {
getFirstItem,
getItems,
getLastItem,
getViewportMaxCoordinate,
recombineByOffset,
} from '../src/store/vp/viewport.helpers';

type ItemsToUpdate = Pick<ViewportStateItems, 'items' | 'start' | 'end'>;

Expand All @@ -26,6 +32,30 @@ describe('revo-grid-viewport', () => {

it('Items are ready for recombination', () => expect(items).toBeDefined());

it('keeps one row of render overscan before the real bottom', () => {
expect(
getViewportMaxCoordinate(
{
realSize,
originItemSize,
},
virtualSize,
),
).toBe(realSize - virtualSize - originItemSize);
});

it('keeps max coordinate at zero when rows fit inside the viewport', () => {
expect(
getViewportMaxCoordinate(
{
realSize: 300,
originItemSize,
},
virtualSize,
),
).toBe(0);
});

// repeat recombination several time same way as user scroll
for (let i = 0; i < 120; i++) {
describe(`Recombination ${i}`, () => {
Expand Down
Loading