parent
5878e780bc
commit
859f873eb2
@ -0,0 +1,121 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
|
||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { createActiveDom } from '$adom';
|
||||||
|
import { state } from '$libs/reactive';
|
||||||
|
import type { Morfo } from '$uix/morfo';
|
||||||
|
import { Soma } from '$soma/core/soma.svelte';
|
||||||
|
import { createSomaRuntime, type SomaRuntimeSources } from '$soma/runtime.svelte';
|
||||||
|
import type { Orientation } from '../../types';
|
||||||
|
|
||||||
|
import { VirtualListProvider } from './virtual-list-provider.svelte';
|
||||||
|
|
||||||
|
function withEffectRoot<T>(fn: () => T): { result: T; cleanup: () => void } {
|
||||||
|
let result!: T;
|
||||||
|
const cleanup = $effect.root(() => {
|
||||||
|
result = fn();
|
||||||
|
});
|
||||||
|
return { result, cleanup };
|
||||||
|
}
|
||||||
|
|
||||||
|
function installSomaHarness() {
|
||||||
|
const dom = createActiveDom();
|
||||||
|
const soma = {
|
||||||
|
dom,
|
||||||
|
runtime: (morfo: Morfo, sources: Omit<SomaRuntimeSources, 'dom' | 'eventEngine'>) =>
|
||||||
|
createSomaRuntime(morfo, {
|
||||||
|
dom,
|
||||||
|
translate: (key) => key,
|
||||||
|
...sources
|
||||||
|
})
|
||||||
|
} as unknown as Soma;
|
||||||
|
|
||||||
|
vi.spyOn(Soma, 'require').mockReturnValue(soma);
|
||||||
|
vi.spyOn(VirtualListProvider.ctx, 'set').mockImplementation((value) => value);
|
||||||
|
|
||||||
|
return { dom };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVirtualListOpts() {
|
||||||
|
return {
|
||||||
|
id: state('virtual-list-root'),
|
||||||
|
ref: state<HTMLElement | null>(null),
|
||||||
|
count: state(10),
|
||||||
|
itemSize: state<number | undefined>(20),
|
||||||
|
estimateSize: state<(index: number) => number>(() => 20),
|
||||||
|
overscan: state(1),
|
||||||
|
orientation: state<Orientation>('vertical'),
|
||||||
|
getItemKey: state<(index: number) => string | number>((index) => `row-${index}`)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('VirtualListProvider', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('computes fixed-size virtual items from viewport and overscan', () => {
|
||||||
|
const { dom } = installSomaHarness();
|
||||||
|
const opts = createVirtualListOpts();
|
||||||
|
|
||||||
|
const { result: provider, cleanup } = withEffectRoot(() => VirtualListProvider.create(opts));
|
||||||
|
provider.viewportSize = 100;
|
||||||
|
provider.scrollOffset = 50;
|
||||||
|
|
||||||
|
expect(provider.totalSize).toBe(200);
|
||||||
|
expect(provider.virtualItems.map((item) => item.index)).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||||
|
expect(provider.virtualItems[0]).toEqual({
|
||||||
|
index: 1,
|
||||||
|
start: 20,
|
||||||
|
size: 20,
|
||||||
|
key: 'row-1'
|
||||||
|
});
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('routes scrollToIndex through ActiveDom for internal viewports', () => {
|
||||||
|
const { dom } = installSomaHarness();
|
||||||
|
const opts = createVirtualListOpts();
|
||||||
|
const viewport = document.createElement('div');
|
||||||
|
const scrollTo = vi.spyOn(dom, 'scrollTo');
|
||||||
|
|
||||||
|
const { result: provider, cleanup } = withEffectRoot(() => VirtualListProvider.create(opts));
|
||||||
|
provider.viewportRef = viewport;
|
||||||
|
provider.viewportSize = 100;
|
||||||
|
|
||||||
|
provider.scrollToIndex(5, { align: 'center' });
|
||||||
|
|
||||||
|
expect(scrollTo).toHaveBeenCalledWith(viewport, {
|
||||||
|
top: 60,
|
||||||
|
behavior: 'auto'
|
||||||
|
});
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compensates dynamic measurements above the viewport through ActiveDom', () => {
|
||||||
|
const { dom } = installSomaHarness();
|
||||||
|
const opts = createVirtualListOpts();
|
||||||
|
opts.itemSize.current = undefined;
|
||||||
|
const viewport = document.createElement('div');
|
||||||
|
viewport.scrollTop = 50;
|
||||||
|
const scrollTo = vi.spyOn(dom, 'scrollTo');
|
||||||
|
|
||||||
|
const { result: provider, cleanup } = withEffectRoot(() => VirtualListProvider.create(opts));
|
||||||
|
provider.viewportRef = viewport;
|
||||||
|
provider.viewportSize = 100;
|
||||||
|
provider.scrollOffset = 50;
|
||||||
|
|
||||||
|
provider.measureItem(0, 30);
|
||||||
|
|
||||||
|
expect(scrollTo).toHaveBeenCalledWith(viewport, { top: 60 });
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in new issue