Skip to content

Commit

Permalink
e2e: scroll-basic spec
Browse files Browse the repository at this point in the history
  • Loading branch information
dhilt committed May 24, 2024
1 parent 3acaace commit 2e33be5
Show file tree
Hide file tree
Showing 9 changed files with 501 additions and 415 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
node-version: 20

- name: Install dependencies
run: npm install
run: npm ci

- name: Build vscroll
run: npm run build
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/demo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 12 additions & 0 deletions app/static/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
</style>
<script>window.__vscroll__ = {};</script>
<script src="vscroll.js"></script>
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion tests/e2e/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { VSCROLL } from './misc/types';
import { VSCROLL, TESTS } from './misc/types';

declare global {
interface Window {
__vscroll__: VSCROLL;
__tests__: TESTS;
}
}
3 changes: 2 additions & 1 deletion tests/e2e/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import './initialization.spec';
import './initialization.spec';
import './scroll-basic.spec';
261 changes: 185 additions & 76 deletions tests/e2e/misc/itemsCounter.ts
Original file line number Diff line number Diff line change
@@ -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;
});
34 changes: 34 additions & 0 deletions tests/e2e/misc/types.ts
Original file line number Diff line number Diff line change
@@ -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<typeof Workflow>;
Expand All @@ -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<Custom = void> = {
datasourceClass?: { new(): unknown };
datasourceName?: string;
datasourceSettings: Settings;
datasourceDevSettings: DevSettings;
templateSettings?: TemplateSettings;
toThrow?: boolean;
custom?: Custom;
timeout?: number;
}

export type It<T = unknown> = (args: { config: Config<T>, page: Page }) => Promise<void>;

export type MakeTest<T = unknown> = (args: { title: string; config: Config<T>; it: It<T> }) => void;
Loading

0 comments on commit 2e33be5

Please sign in to comment.