diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82f9e0c2..4e6f30b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: node-version: 20 - name: Install dependencies - run: npm install + run: npm ci - name: Build vscroll run: npm run build diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index e1c3db5e..e232d976 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -14,15 +14,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Install dependencies - run: npm install + run: npm ci - name: Build vscroll run: npm run build diff --git a/app/static/demo.html b/app/static/demo.html index 098cb14a..ebd798c9 100644 --- a/app/static/demo.html +++ b/app/static/demo.html @@ -24,6 +24,18 @@ .viewport .item span { font-size: small; } + + .viewport.horizontal { + width: 200px; + height: 100px; + overflow-x: scroll; + overflow-y: hidden; + white-space: nowrap; + } + + .viewport.horizontal div { + display: inline-block; + } diff --git a/package.json b/package.json index 88a54c89..e2ad5fe3 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "lint": "eslint . --ext .ts", "jest": "jest ./tests/unit/", "watch": "jest ./tests/unit/settings.spec.ts --watch", + "tsc": "tsc --project ./tsconfig.json", "build": "node build", "test": "npm run lint && npm run jest", "app": "node app/server.js", diff --git a/tests/e2e/index.d.ts b/tests/e2e/index.d.ts index ad1412ee..93775e9c 100644 --- a/tests/e2e/index.d.ts +++ b/tests/e2e/index.d.ts @@ -1,7 +1,8 @@ -import { VSCROLL } from './misc/types'; +import { VSCROLL, TESTS } from './misc/types'; declare global { interface Window { __vscroll__: VSCROLL; + __tests__: TESTS; } } \ No newline at end of file diff --git a/tests/e2e/index.ts b/tests/e2e/index.ts index b86fe813..7e962c81 100644 --- a/tests/e2e/index.ts +++ b/tests/e2e/index.ts @@ -1 +1,2 @@ -import './initialization.spec'; \ No newline at end of file +import './initialization.spec'; +import './scroll-basic.spec'; \ No newline at end of file diff --git a/tests/e2e/misc/itemsCounter.ts b/tests/e2e/misc/itemsCounter.ts index 92a90ccb..b3d9aa8f 100644 --- a/tests/e2e/misc/itemsCounter.ts +++ b/tests/e2e/misc/itemsCounter.ts @@ -1,88 +1,197 @@ -import { Direction } from '../../../src/index'; - -export class ItemsDirCounter { - count: number; - index: number; - padding: number; - paddingShift?: number; - size: number; - - constructor(count = 0, padding = 0) { - this.count = count; - this.padding = padding; - this.paddingShift = 0; - this.index = NaN; - this.size = NaN; - } + +import { Page } from '@playwright/test'; +import { Direction } from '../../../src/inputs/common'; +import { TESTS } from './types'; + +export type ScrollResult = { + edgeItemIndex: (number | undefined)[], + oppositeItemIndex: (number | undefined)[], + paddingSize: (number | undefined)[], + oppositePaddingSize: (number | undefined)[], } -export class ItemsCounter { - direction: Direction | null; // direction per calculations - backward: ItemsDirCounter; - forward: ItemsDirCounter; - average: number; +export type ItemsCounter = { + invertDirection: (direction: Direction) => void, + doScrollMax: (direction: Direction) => void, + getInitialItemsCounter: () => void, + getCurrentItemsCounter: (direction: Direction) => void, + getExpectations: (direction: Direction) => ScrollResult, +} - get total(): number { - return this.forward.index - this.backward.index + 1; - // return this.backward.count + this.forward.count; - } +export const initializeItemsCounter = (page: Page) => page.evaluate(() => { + const { workflow: { scroller } } = window['__vscroll__']; + const forward = 'forward' as Direction; + const backward = 'backward' as Direction; + let itemsCounter: ItemsCounter; - get paddings(): number { - return this.forward.padding + this.backward.padding; - } + class ItemsDirCounter { + count: number; + index: number; + padding: number; + paddingShift?: number; + size: number; - constructor(direction?: Direction) { - this.direction = direction || null; - this.forward = new ItemsDirCounter(); - this.backward = new ItemsDirCounter(); - this.average = NaN; + constructor(count = 0, padding = 0) { + this.count = count; + this.padding = padding; + this.paddingShift = 0; + this.index = NaN; + this.size = NaN; + } } - get(token: Direction): ItemsDirCounter { - return token === Direction.backward ? this.backward : this.forward; + class ItemsCounter { + direction: Direction | null; // direction per calculations + backward: ItemsDirCounter; + forward: ItemsDirCounter; + average: number; + + get total(): number { + return this.forward.index - this.backward.index + 1; + // return this.backward.count + this.forward.count; + } + + get paddings(): number { + return this.forward.padding + this.backward.padding; + } + + constructor(direction?: Direction) { + this.direction = direction || null; + this.forward = new ItemsDirCounter(); + this.backward = new ItemsDirCounter(); + this.average = NaN; + } + + get(token: Direction): ItemsDirCounter { + return token === backward ? this.backward : this.forward; + } + + set(token: Direction, value: ItemsDirCounter): void { + if (token === backward) { + Object.assign(this.backward, value); + } else { + Object.assign(this.forward, value); + } + } } - set(token: Direction, value: ItemsDirCounter): void { - if (token === Direction.backward) { - Object.assign(this.backward, value); + const invertDirection = (direction: Direction) => { + const _forward = direction === forward; + direction = _forward ? backward : forward; + }; + + const doScrollMax = (direction: Direction) => { + if (direction === forward) { + scroller.adapter.fix({ scrollPosition: Infinity }); } else { - Object.assign(this.forward, value); + scroller.adapter.fix({ scrollPosition: 0 }); } - } -} + }; -export const testItemsCounter = (startIndex: number, misc, itemsCounter: ItemsCounter): void => { - const bwdSize = itemsCounter.backward.size; - const fwdSize = itemsCounter.forward.size; - const bwdPadding = itemsCounter.backward.padding; - const fwdPadding = itemsCounter.forward.padding + (itemsCounter.forward.paddingShift || 0); - const average = itemsCounter.average; - const elements = misc.getElements(); - const { viewport, buffer, adapter } = misc.scroller; - const { bufferInfo, firstVisible } = adapter; - - let sizePaddings = 0; - if (!isNaN(Number(bwdPadding))) { - expect(bwdPadding).toEqual(viewport.paddings.backward.size); - sizePaddings += bwdPadding; - } - if (!isNaN(Number(fwdPadding))) { - expect(fwdPadding).toEqual(viewport.paddings.forward.size); - sizePaddings += fwdPadding; - } - if (!isNaN(Number(bwdSize)) && !isNaN(Number(fwdSize))) { - const size = misc.getScrollableSize(); - expect(bwdSize + fwdSize + sizePaddings).toEqual(size); - } - if (!isNaN(Number(average))) { - expect(average).toEqual(buffer.defaultSize); - } - expect(elements.length).toEqual(itemsCounter.total); - expect(buffer.items.length).toEqual(itemsCounter.total); - expect(misc.getElementIndex(elements[0])).toEqual(itemsCounter.backward.index); - expect(misc.getElementIndex(elements[elements.length - 1])).toEqual(itemsCounter.forward.index); - expect(misc.checkElementContentByIndex(startIndex)).toEqual(true); - expect(bufferInfo.firstIndex).toEqual(itemsCounter.backward.index); - expect(bufferInfo.lastIndex).toEqual(itemsCounter.forward.index); - expect(firstVisible.$index).toEqual(startIndex); -}; + const getInitialItemsCounter = () => { + const { startIndex } = scroller.settings; + const edgeItem = scroller.buffer.getEdgeVisibleItem(forward); + const oppositeItem = scroller.buffer.getEdgeVisibleItem(backward); + const result = new ItemsCounter(); + if (!edgeItem || !oppositeItem) { + return result; + } + result.set(forward, { + count: edgeItem.$index - startIndex + 1, + index: edgeItem.$index, + padding: 0, + size: 0 + }); + result.set(backward, { + count: startIndex - oppositeItem.$index, + index: oppositeItem.$index, + padding: 0, + size: NaN + }); + itemsCounter = result; + }; + + const getFullHouseDiff = ( + viewportSize: number, paddingDelta: number, itemSize: number, bufferSize: number + ): number => { + const sizeToFill = viewportSize + 2 * paddingDelta; // size to fill the viewport + padding deltas + const itemsToFillNotRounded = sizeToFill / itemSize; + const itemsToFillRounded = Math.ceil(sizeToFill / itemSize); + const itemsToFill = itemsToFillRounded + (itemsToFillNotRounded === itemsToFillRounded ? 0 : 1); + const bufferSizeDiff = bufferSize - itemsToFill; + return Math.max(0, bufferSizeDiff); + }; + + + const getCurrentItemsCounter = (direction: Direction) => { + const previous = itemsCounter; + const { bufferSize, padding } = scroller.settings; + const viewportSize = scroller.viewport.getSize(); + const itemSize = scroller.buffer.defaultSize; + const fwd = direction === forward; + const opposite = fwd ? backward : forward; + const delta = viewportSize * padding; + + // handle direction (fetch) + const fullHouseDiff = getFullHouseDiff(viewportSize, delta, itemSize, bufferSize); + const _singleFetchCount = Math.ceil(delta / itemSize); + const singleFetchCount = Math.max(bufferSize, _singleFetchCount); + const itemsToFetch = previous.direction && previous.direction !== direction ? + (_singleFetchCount + fullHouseDiff) : singleFetchCount; + const previousEdgeIndex = previous.get(direction).index; + const paddingItems = (fwd ? 1 : -1) * (previous.get(direction).padding / itemSize); + const newItemsPack = (fwd ? 1 : -1) * itemsToFetch; + const newDirIndex = previousEdgeIndex + paddingItems + newItemsPack; + + // handle opposite (clip) + const oppPadding = previous.get(opposite).padding; + const previousTotalSize = previous.total * itemSize + previous.paddings; + const sizeToClip = previousTotalSize - oppPadding - viewportSize - delta; + const itemsToClip = Math.floor(sizeToClip / itemSize); + const newOppIndex = previous.get(opposite).index + (fwd ? 1 : -1) * itemsToClip; + const newOppPadding = itemsToClip * itemSize + oppPadding; + + const result = new ItemsCounter(direction); + result.set(direction, { + index: newDirIndex, + padding: 0, + count: NaN, + size: NaN + }); + result.set(opposite, { + index: newOppIndex, + padding: newOppPadding, + count: NaN, + size: NaN + }); + itemsCounter = result; + }; + + const getExpectations = (direction: Direction): ScrollResult => { + const opposite = direction === forward ? backward : forward; + const edgeItem = scroller.buffer.getEdgeVisibleItem(direction); + const oppositeItem = scroller.buffer.getEdgeVisibleItem(opposite); + const edgeItemIndex = itemsCounter.get(direction).index; + const oppositeItemIndex = itemsCounter.get(opposite).index; + const paddingSize = scroller.viewport.paddings.byDirection(direction).size; + const oppositePaddingSize = scroller.viewport.paddings.byDirection(direction, true).size; + + return { + edgeItemIndex: [edgeItemIndex, edgeItem?.$index], + oppositeItemIndex: [oppositeItemIndex, oppositeItem?.$index], + paddingSize: [itemsCounter.get(direction).padding, paddingSize], + oppositePaddingSize: [itemsCounter.get(opposite).padding, oppositePaddingSize] + }; + }; + + const ItemsCounterUtils: TESTS['ItemsCounter'] = { + invertDirection, + doScrollMax, + getInitialItemsCounter, + getCurrentItemsCounter, + getExpectations + }; + + window['__tests__'] ??= {} as TESTS; + window['__tests__'].ItemsCounter = ItemsCounterUtils; +}); \ No newline at end of file diff --git a/tests/e2e/misc/types.ts b/tests/e2e/misc/types.ts index 8e001aae..78a13986 100644 --- a/tests/e2e/misc/types.ts +++ b/tests/e2e/misc/types.ts @@ -1,4 +1,7 @@ +import { Page } from '@playwright/test'; +import { Settings, DevSettings } from '../../../src/interfaces'; import { Workflow, IDatasourceConstructed } from '../../../src/index'; +import { ItemsCounter } from './itemsCounter'; interface Scroller { workflow: InstanceType; @@ -16,3 +19,34 @@ export type VSCROLL = { scroller1: Scroller; scroller2: Scroller; }; + +export type TESTS = { + ItemsCounter: ItemsCounter +}; + +type TemplateSettings = { + noViewportClass?: boolean; + viewportHeight?: number; + viewportWidth?: number | null; + itemHeight?: number; + itemWidth?: number | null; + horizontal?: boolean; + dynamicSize?: string | null; + viewportPadding?: number; + headerHeight?: number; +} + +export type Config = { + datasourceClass?: { new(): unknown }; + datasourceName?: string; + datasourceSettings: Settings; + datasourceDevSettings: DevSettings; + templateSettings?: TemplateSettings; + toThrow?: boolean; + custom?: Custom; + timeout?: number; +} + +export type It = (args: { config: Config, page: Page }) => Promise; + +export type MakeTest = (args: { title: string; config: Config; it: It }) => void; \ No newline at end of file diff --git a/tests/e2e/scroll-basic.spec.ts b/tests/e2e/scroll-basic.spec.ts index e78a3a9f..3a62d1d6 100644 --- a/tests/e2e/scroll-basic.spec.ts +++ b/tests/e2e/scroll-basic.spec.ts @@ -1,251 +1,152 @@ -import { test, expect } from '@playwright/test'; +import { test, expect, Page } from '@playwright/test'; +import { ScrollResult, initializeItemsCounter } from './misc/itemsCounter'; +import { Config, It, MakeTest } from './misc/types'; +import { Direction } from '../../src/inputs/common'; -import { Direction } from '../../src/index'; -import { ItemsCounter } from './misc/itemsCounter'; +// test.use({ headless: false }); -const configList = [{ +const URL = '127.0.0.1:3000'; + +interface ICustom { + direction: Direction; + count: number; + bouncing?: boolean; + mass?: boolean; +} + +const configList: Config[] = [{ datasourceSettings: { startIndex: 100, bufferSize: 4, padding: 0.22, itemSize: 20 }, templateSettings: { viewportHeight: 71, itemHeight: 20 }, + datasourceDevSettings: { debug: true }, custom: { direction: Direction.forward, count: 1 } -}/*, { +}, { datasourceSettings: { startIndex: 1, bufferSize: 5, padding: 0.2, itemSize: 20 }, templateSettings: { viewportHeight: 100 }, + datasourceDevSettings: { debug: true }, custom: { direction: Direction.forward, count: 1 } }, { datasourceSettings: { startIndex: -15, bufferSize: 12, padding: 0.98, itemSize: 20 }, templateSettings: { viewportHeight: 66, itemHeight: 20 }, + datasourceDevSettings: { debug: true }, custom: { direction: Direction.forward, count: 1 } }, { datasourceSettings: { startIndex: 1, bufferSize: 5, padding: 1, horizontal: true, itemSize: 100 }, templateSettings: { viewportWidth: 450, itemWidth: 100, horizontal: true }, + datasourceDevSettings: { debug: true }, custom: { direction: Direction.forward, count: 1 } }, { datasourceSettings: { startIndex: -74, bufferSize: 4, padding: 0.72, horizontal: true, itemSize: 75 }, templateSettings: { viewportWidth: 300, itemWidth: 75, horizontal: true }, + datasourceDevSettings: { debug: true }, custom: { direction: Direction.forward, count: 1 } -}*/]; - -// const treatIndex = (index: number) => index <= 3 ? index : (3 * 2 - index); - -// const singleBackwardMaxScrollConfigList = -// configList.map(config => ({ -// ...config, -// custom: { -// ...config.custom, -// direction: Direction.backward -// } -// })); - -// const massForwardScrollsConfigList = -// configList.map((config, index) => ({ -// ...config, -// custom: { -// direction: Direction.backward, -// count: 3 + treatIndex(index) // 3-6 bwd scroll events per config -// } -// })); - -// const massBackwardScrollsConfigList = -// massForwardScrollsConfigList.map((config, index) => ({ -// ...config, -// custom: { -// direction: Direction.backward, -// count: 3 + treatIndex(index) // 3-6 fwd scroll events per config -// } -// })); - -// const massBouncingScrollsConfigList_fwd = -// massForwardScrollsConfigList.map((config, index) => ({ -// ...config, -// custom: { -// direction: Direction.forward, -// count: (3 + treatIndex(index)) * 2, // 3-6 (fwd + bwd) scroll events per config -// bouncing: true -// } -// })); - -// const massBouncingScrollsConfigList_bwd = -// massForwardScrollsConfigList.map((config, index) => ({ -// ...config, -// custom: { -// direction: Direction.backward, -// count: (3 + treatIndex(index)) * 2, // 3-6 (fwd + bwd) scroll events per config -// bouncing: true -// } -// })); - -// const massTwoDirectionalScrollsConfigList_fwd = -// massForwardScrollsConfigList.map((config, index) => ({ -// ...config, -// custom: { -// direction: Direction.forward, -// count: (3 + treatIndex(index)) * 2, // 3-6 fwd + 3-6 bwd scroll events per config -// mass: true -// } -// })); - -// const massTwoDirectionalScrollsConfigList_bwd = -// massForwardScrollsConfigList.map((config, index) => ({ -// ...config, -// custom: { -// direction: Direction.backward, -// count: (3 + treatIndex(index)) * 2, // 3-6 fwd + 3-6 bwd scroll events per config -// mass: true -// } -// })); - -// const doScrollMax = (config, misc) => { -// if (config.custom.direction === Direction.forward) { -// misc.scrollMax(); -// } else { -// misc.scrollMin(); -// } -// }; - -// const invertDirection = (config) => { -// const _forward = config.custom.direction === Direction.forward; -// config.custom.direction = _forward ? Direction.backward : Direction.forward; -// }; - -// const getFullHouseDiff = ( -// viewportSize: number, paddingDelta: number, itemSize: number, bufferSize: number -// ): number => { -// const sizeToFill = viewportSize + 2 * paddingDelta; // size to fill the viewport + padding deltas -// const itemsToFillNotRounded = sizeToFill / itemSize; -// const itemsToFillRounded = Math.ceil(sizeToFill / itemSize); -// const itemsToFill = itemsToFillRounded + (itemsToFillNotRounded === itemsToFillRounded ? 0 : 1); -// const bufferSizeDiff = bufferSize - itemsToFill; -// return Math.max(0, bufferSizeDiff); -// }; - -const shouldScroll = config => async (page) => { - const custom = config.custom; - const wfCount = custom.count + 1; - const wfCountMiddle = Math.ceil(wfCount / 2); - let itemsCounter: ItemsCounter; - - const result = await page.evaluate(({ custom }) => { - const { workflow } = window['__vscroll__'].workflow; - - const finalize = workflow.finalize; - workflow.finalize = (...args) => { - finalize.apply(workflow, args); - - const cycles = workflow.cyclesDone; - if (cycles === 1) { - itemsCounter = ((scroller) => { - const { startIndex } = scroller.settings; - const edgeItem = scroller.buffer.getEdgeVisibleItem(Direction.forward); - const oppositeItem = scroller.buffer.getEdgeVisibleItem(Direction.backward); - const result = new ItemsCounter(); - if (!edgeItem || !oppositeItem) { - return result; - } - result.set(Direction.forward, { - count: edgeItem.$index - startIndex + 1, - index: edgeItem.$index, - padding: 0, - size: 0 - }); - result.set(Direction.backward, { - count: startIndex - oppositeItem.$index, - index: oppositeItem.$index, - padding: 0, - size: NaN - }); - return result; - })(workflow.scroller); - - } else { - const getFullHouseDiff = ( - viewportSize: number, paddingDelta: number, itemSize: number, bufferSize: number - ): number => { - const sizeToFill = viewportSize + 2 * paddingDelta; // size to fill the viewport + padding deltas - const itemsToFillNotRounded = sizeToFill / itemSize; - const itemsToFillRounded = Math.ceil(sizeToFill / itemSize); - const itemsToFill = itemsToFillRounded + (itemsToFillNotRounded === itemsToFillRounded ? 0 : 1); - const bufferSizeDiff = bufferSize - itemsToFill; - return Math.max(0, bufferSizeDiff); - }; - - itemsCounter = ((scroller, direction: Direction, previous: ItemsCounter): ItemsCounter => { - const { bufferSize, padding } = scroller.settings; - const viewportSize = scroller.viewport.getSize(); - const itemSize = scroller.buffer.defaultSize; - const fwd = direction === Direction.forward; - const opposite = fwd ? Direction.backward : Direction.forward; - const delta = viewportSize * padding; - - // handle direction (fetch) - const fullHouseDiff = getFullHouseDiff(viewportSize, delta, itemSize, bufferSize); - const _singleFetchCount = Math.ceil(delta / itemSize); - const singleFetchCount = Math.max(bufferSize, _singleFetchCount); - const itemsToFetch = previous.direction && previous.direction !== direction ? - (_singleFetchCount + fullHouseDiff) : singleFetchCount; - const previousEdgeIndex = previous.get(direction).index; - const paddingItems = (fwd ? 1 : -1) * (previous.get(direction).padding / itemSize); - const newItemsPack = (fwd ? 1 : -1) * itemsToFetch; - const newDirIndex = previousEdgeIndex + paddingItems + newItemsPack; - - // handle opposite (clip) - const oppPadding = previous.get(opposite).padding; - const previousTotalSize = previous.total * itemSize + previous.paddings; - const sizeToClip = previousTotalSize - oppPadding - viewportSize - delta; - const itemsToClip = Math.floor(sizeToClip / itemSize); - const newOppIndex = previous.get(opposite).index + (fwd ? 1 : -1) * itemsToClip; - const newOppPadding = itemsToClip * itemSize + oppPadding; - - const result = new ItemsCounter(direction); - result.set(direction, { - index: newDirIndex, - padding: 0, - count: NaN, - size: NaN - }); - result.set(opposite, { - index: newOppIndex, - padding: newOppPadding, - count: NaN, - size: NaN - }); - return result; - })(workflow.scroller, custom.direction, itemsCounter); - } - - if (cycles < wfCount) { - const invertDirection = () => { - const _forward = custom.direction === Direction.forward; - custom.direction = _forward ? Direction.backward : Direction.forward; - }; - if (custom.bouncing) { - invertDirection(); - } else if (custom.mass) { - if (cycles === wfCountMiddle) { - invertDirection(); - } +}]; + +const treatIndex = (index: number) => index <= 3 ? index : (3 * 2 - index); + +const singleBackwardMaxScrollConfigList = + configList.map(config => ({ + ...config, + custom: { + ...config.custom, + direction: Direction.backward + } + } as Config)); + +const massForwardScrollsConfigList = + configList.map((config, index) => ({ + ...config, + custom: { + direction: Direction.backward, + count: 3 + treatIndex(index) // 3-6 bwd scroll events per config + } + } as Config)); + +const massBackwardScrollsConfigList = + massForwardScrollsConfigList.map((config, index) => ({ + ...config, + custom: { + direction: Direction.backward, + count: 3 + treatIndex(index) // 3-6 fwd scroll events per config + } + } as Config)); + +const massBouncingScrollsConfigList_fwd = + massForwardScrollsConfigList.map((config, index) => ({ + ...config, + custom: { + direction: Direction.forward, + count: (3 + treatIndex(index)) * 2, // 3-6 (fwd + bwd) scroll events per config + bouncing: true + } + } as Config)); + +const massBouncingScrollsConfigList_bwd = + massForwardScrollsConfigList.map((config, index) => ({ + ...config, + custom: { + direction: Direction.backward, + count: (3 + treatIndex(index)) * 2, // 3-6 (fwd + bwd) scroll events per config + bouncing: true + } + } as Config)); + +const massTwoDirectionalScrollsConfigList_fwd = + massForwardScrollsConfigList.map((config, index) => ({ + ...config, + custom: { + direction: Direction.forward, + count: (3 + treatIndex(index)) * 2, // 3-6 fwd + 3-6 bwd scroll events per config + mass: true + } + } as Config)); + +const massTwoDirectionalScrollsConfigList_bwd = + massForwardScrollsConfigList.map((config, index) => ({ + ...config, + custom: { + direction: Direction.backward, + count: (3 + treatIndex(index)) * 2, // 3-6 fwd + 3-6 bwd scroll events per config + mass: true + } + } as Config)); + + +const shouldScroll = async (config: Config, page: Page) => { + + await initializeItemsCounter(page); + + const result = await page.evaluate(custom => + new Promise(resolve => { + const { workflow } = window['__vscroll__']; + const { ItemsCounter: helper } = window['__tests__']; + + workflow.scroller.state.cycle.busy.on(busy => { + if (busy) { + return; + } + + if (workflow.cyclesDone === 1) { + helper.getInitialItemsCounter(); + } else { + helper.getCurrentItemsCounter(custom.direction); } - if (custom.direction === Direction.forward) { - workflow.scroller.adapter.fix({ scrollPosition: Infinity }); + + const wfCount = custom.count + 1; + if (workflow.cyclesDone < wfCount) { + if (custom.bouncing) { + helper.invertDirection(custom.direction); + } else if (custom.mass) { + const wfCountMiddle = Math.ceil(wfCount / 2); + if (workflow.cyclesDone === wfCountMiddle) { + helper.invertDirection(custom.direction); + } + } + helper.doScrollMax(custom.direction); } else { - workflow.scroller.adapter.fix({ scrollPosition: 0 }); + resolve(helper.getExpectations(custom.direction)); } - } else { - // expectations - const direction: Direction = custom.direction; - const opposite = direction === Direction.forward ? Direction.backward : Direction.forward; - const edgeItem = workflow.scroller.buffer.getEdgeVisibleItem(direction); - const oppositeItem = workflow.scroller.buffer.getEdgeVisibleItem(opposite); - const edgeItemIndex = itemsCounter.get(direction).index; - const oppositeItemIndex = itemsCounter.get(opposite).index; - return { - edgeItemsIndex: [edgeItemIndex, edgeItem?.$index], - oppositeItemIndex: [oppositeItemIndex, oppositeItem?.$index], - paddingSize: itemsCounter.get(direction).padding, - oppositePaddingSize: itemsCounter.get(opposite).padding - }; - } - }; - }, { custom }); + }); + }), config.custom as ICustom); const { edgeItemIndex, @@ -256,121 +157,148 @@ const shouldScroll = config => async (page) => { expect(edgeItemIndex?.[0]).toEqual(edgeItemIndex?.[1]); expect(oppositeItemIndex?.[0]).toEqual(oppositeItemIndex?.[1]); + expect(paddingSize?.[0]).toEqual(paddingSize?.[1]); + expect(oppositePaddingSize?.[0]).toEqual(oppositePaddingSize?.[1]); - const _paddingSize = await page.evaluate((direction) => - document.querySelector(`[data-padding-${direction}]`)?.clientHeight - , custom.direction); - - const _oppositePaddingSize = await page.evaluate((direction) => - document.querySelector(`[data-padding-${direction}]`)?.clientHeight - , custom.direction === Direction.forward - ? Direction.backward - : Direction.forward - ); - - expect(_paddingSize).toEqual(paddingSize); - expect(_oppositePaddingSize).toEqual(oppositePaddingSize); - - await expect(page.locator(`[sid="${edgeItemIndex[0]}"]`)) - .toHaveText('item: ' + edgeItemIndex[0]); - await expect(page.locator(`[sid="${oppositeItemIndex[0]}"]`)) - .toHaveText('item: ' + oppositeItemIndex[0]); + await expect(page.locator(`[data-sid="${edgeItemIndex[0]}"]`)) + .toHaveText(`${edgeItemIndex[0]}) item #${edgeItemIndex[0]}`); + await expect(page.locator(`[data-sid="${oppositeItemIndex[0]}"]`)) + .toHaveText(`${oppositeItemIndex[0]}) item #${oppositeItemIndex[0]}`); }; -const runScroller = async (page, { settings = {}, devSettings = {} } = {}) => - await page.evaluate(({ settings, devSettings }) => { +const runScroller = async (page: Page, config: Config) => + await page.evaluate(config => { + const { datasourceSettings, datasourceDevSettings, templateSettings } = config as Config; const { Scroller, datasource } = window['__vscroll__']; - datasource.settings = { ...datasource.settings, ...settings }; - datasource.devSettings = { ...datasource.devSettings, ...devSettings }; + datasource.settings = { ...datasource.settings, ...datasourceSettings }; + datasource.devSettings = { ...datasource.devSettings, ...datasourceDevSettings }; + + const viewport = window.document.querySelector('.viewport') as HTMLElement; + if (templateSettings?.viewportWidth) { + viewport.style.width = templateSettings.viewportWidth + 'px'; + } + if (templateSettings?.viewportHeight) { + viewport.style.height = templateSettings.viewportHeight + 'px'; + } + if (templateSettings?.horizontal) { + viewport.className += ' horizontal'; + } + let styles = ''; + if (templateSettings?.itemWidth) { + styles += ` + .viewport .item { + width: ${templateSettings.itemWidth}px; + }`; + } + if (templateSettings?.itemHeight) { + styles += ` + .viewport .item { + height: ${templateSettings.itemHeight}px; + }`; + } + if (styles) { + const styleSheet = document.createElement('style'); + styleSheet.innerText = styles; + document.head.appendChild(styleSheet); + } + const { workflow } = new Scroller(datasource); window['__vscroll__'].workflow = workflow; - }, { settings, devSettings }); + }, config as unknown); + -const makeTest = async ({ page, title, config, it }) => { - console.log(title); +const makeTest: MakeTest = ({ title, config, it }) => + test(title, ({ page }) => it({ config, page })); + +const shouldWork: It = async ({ config, page }) => { await page.goto(URL + '/need-run'); - await runScroller(page, { settings: config.datasourceSettings }); - await it(page); + await runScroller(page, config); + await shouldScroll(config, page); + // await new Promise(r => setTimeout(r, 2000)); }; +test.describe('Scroll Basic Spec', () => { + test.describe.configure({ mode: 'serial' }); + + test.describe('Single max fwd scroll event', () => + configList.forEach((config, index) => + makeTest({ + config, + title: `should process 1 forward max scroll (${index + 1})`, + it: shouldWork + }) + ) + ); + + test.describe('Single max bwd scroll event', () => + singleBackwardMaxScrollConfigList.forEach((config, index) => + makeTest({ + config, + title: `should process 1 backward max scroll (${index + 1})`, + it: shouldWork + }) + ) + ); + + test.describe('Mass max fwd scroll events', () => + massForwardScrollsConfigList.forEach((config, index) => + makeTest({ + config, + title: `should process some forward scrolls (${index + 1})`, + it: shouldWork + }) + ) + ); + + test.describe('Mass max bwd scroll events', () => + massBackwardScrollsConfigList.forEach((config, index) => + makeTest({ + config, + title: `should process some backward scrolls (${index + 1})`, + it: shouldWork + }) + ) + ); + + test.describe('Bouncing max two-directional scroll events (fwd started)', () => + massBouncingScrollsConfigList_fwd.forEach((config, index) => + makeTest({ + config, + title: `should process some bouncing scrolls (${index + 1})`, + it: shouldWork + }) + ) + ); + + test.describe('Bouncing max two-directional scroll events (bwd started)', () => + massBouncingScrollsConfigList_bwd.forEach((config, index) => + makeTest({ + config, + title: `should process some bouncing scrolls (${index + 1})`, + it: shouldWork + }) + ) + ); + + test.describe('Mass max two-directional scroll events (fwd started)', () => + massTwoDirectionalScrollsConfigList_fwd.forEach((config, index) => + makeTest({ + config, + title: `should process some two-directional scrolls (${index + 1})`, + it: shouldWork + }) + ) + ); + + test.describe('Mass max two-directional scroll events (bwd started)', () => + massTwoDirectionalScrollsConfigList_bwd.forEach((config, index) => + makeTest({ + config, + title: `should process some two-directional scrolls (${index + 1})`, + it: shouldWork + }) + ) + ); +}); -test('Single max fwd scroll event', ({ page }) => - configList.forEach(config => - makeTest({ - page, - config, - title: 'should process 1 forward max scroll', - it: shouldScroll(config) - }) - ) -); - - // describe('Single max bwd scroll event', () => - // singleBackwardMaxScrollConfigList.forEach(config => - // _makeTest({ - // config, - // title: 'should process 1 backward max scroll', - // it: shouldScroll(config) - // }) - // ) - // ); - - // describe('Mass max fwd scroll events', () => - // massForwardScrollsConfigList.forEach(config => - // _makeTest({ - // config, - // title: 'should process some forward scrolls', - // it: shouldScroll(config) - // }) - // ) - // ); - - // describe('Mass max bwd scroll events', () => - // massBackwardScrollsConfigList.forEach(config => - // _makeTest({ - // config, - // title: 'should process some backward scrolls', - // it: shouldScroll(config) - // }) - // ) - // ); - - // describe('Bouncing max two-directional scroll events (fwd started)', () => - // massBouncingScrollsConfigList_fwd.forEach(config => - // _makeTest({ - // config, - // title: 'should process some bouncing scrolls', - // it: shouldScroll(config) - // }) - // ) - // ); - - // describe('Bouncing max two-directional scroll events (bwd started)', () => - // massBouncingScrollsConfigList_bwd.forEach(config => - // _makeTest({ - // config, - // title: 'should process some bouncing scrolls', - // it: shouldScroll(config) - // }) - // ) - // ); - - // describe('Mass max two-directional scroll events (fwd started)', () => - // massTwoDirectionalScrollsConfigList_fwd.forEach(config => - // _makeTest({ - // config, - // title: 'should process some two-directional scrolls', - // it: shouldScroll(config) - // }) - // ) - // ); - - // describe('Mass max two-directional scroll events (bwd started)', () => - // massTwoDirectionalScrollsConfigList_bwd.forEach(config => - // _makeTest({ - // config, - // title: 'should process some two-directional scrolls', - // it: shouldScroll(config) - // }) - // ) - // ); +export default {};