test(table): хелпер createFixedRowVirtualizerOptions без чтения layout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2a287480b6
commit
bdb00afc5a
27
web/src/components/RealtimeTable/utils/virtualizerOptions.js
Normal file
27
web/src/components/RealtimeTable/utils/virtualizerOptions.js
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Опции виртуализатора строк с фиксированной высотой.
|
||||
*
|
||||
* measureElement по умолчанию читает layout (getBoundingClientRect/offsetHeight) на каждой строке,
|
||||
* что вызывает синхронный пересчёт раскладки. Здесь он возвращает константу, элемент не читается.
|
||||
* Поэтому высоту строки нужно задавать явно в стилях (строке и ячейке) тем же значением.
|
||||
*
|
||||
* rowHeight: число (px) или функция, возвращающая px (когда высота задана в rem и зависит от ширины окна).
|
||||
*/
|
||||
export const createFixedRowVirtualizerOptions = ({ rowHeight, overscan, extra = {} }) => {
|
||||
const getHeight = typeof rowHeight === 'function' ? rowHeight : () => rowHeight;
|
||||
return {
|
||||
...extra,
|
||||
overscan,
|
||||
estimateSize: () => getHeight(),
|
||||
measureElement: () => getHeight(),
|
||||
};
|
||||
};
|
||||
|
||||
// Размер 1rem в px по правилу App.css: .83vw при ширине окна <= 1920px, иначе 16px.
|
||||
// Считается по window.innerWidth, чтобы не читать layout и не вызывать пересчёт стилей.
|
||||
const REM_BASE_PX = 16;
|
||||
const REM_VW_RATIO = 0.0083;
|
||||
const REM_MAX_VIEWPORT = 1920;
|
||||
|
||||
export const getRootFontSizePx = (viewportWidth = typeof window === 'undefined' ? undefined : window.innerWidth) =>
|
||||
viewportWidth && viewportWidth <= REM_MAX_VIEWPORT ? viewportWidth * REM_VW_RATIO : REM_BASE_PX;
|
||||
@ -0,0 +1,51 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createFixedRowVirtualizerOptions, getRootFontSizePx } from './virtualizerOptions.js';
|
||||
|
||||
const throwingElement = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_target, prop) {
|
||||
throw new Error(`Чтение свойства элемента запрещено: ${String(prop)}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
test('measureElement не обращается к элементу и возвращает фиксированную высоту', () => {
|
||||
const { measureElement } = createFixedRowVirtualizerOptions({ rowHeight: 40, overscan: 3 });
|
||||
assert.equal(measureElement(throwingElement), 40);
|
||||
assert.equal(measureElement(null), 40);
|
||||
});
|
||||
|
||||
test('estimateSize возвращает высоту для любого индекса, overscan пробрасывается', () => {
|
||||
const options = createFixedRowVirtualizerOptions({ rowHeight: 40, overscan: 3 });
|
||||
assert.equal(options.estimateSize(0), 40);
|
||||
assert.equal(options.estimateSize(999), 40);
|
||||
assert.equal(options.overscan, 3);
|
||||
});
|
||||
|
||||
test('rowHeight-функция вычисляется при каждом вызове (rem зависит от ширины окна)', () => {
|
||||
let height = 40;
|
||||
const options = createFixedRowVirtualizerOptions({ rowHeight: () => height, overscan: 5 });
|
||||
assert.equal(options.estimateSize(0), 40);
|
||||
height = 48;
|
||||
assert.equal(options.estimateSize(0), 48);
|
||||
assert.equal(options.measureElement(throwingElement), 48);
|
||||
});
|
||||
|
||||
test('дополнительные опции добавляются и не перекрывают основные', () => {
|
||||
const options = createFixedRowVirtualizerOptions({
|
||||
rowHeight: 40,
|
||||
overscan: 5,
|
||||
extra: { scrollPaddingStart: 0, estimateSize: () => 1 },
|
||||
});
|
||||
assert.equal(options.scrollPaddingStart, 0);
|
||||
assert.equal(options.estimateSize(0), 40);
|
||||
});
|
||||
|
||||
test('getRootFontSizePx повторяет правило App.css', () => {
|
||||
assert.equal(getRootFontSizePx(2560), 16);
|
||||
assert.equal(getRootFontSizePx(1920), 1920 * 0.0083);
|
||||
assert.ok(Math.abs(getRootFontSizePx(1600) * 3 - 39.84) < 0.01);
|
||||
assert.equal(getRootFontSizePx(undefined), 16);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user