From a52515a53c06df16d20f79998724538045f55600 Mon Sep 17 00:00:00 2001 From: Denis Hilt Date: Sat, 7 Nov 2020 21:34:14 +0300 Subject: [PATCH 1/7] issue-171 replace spec draft --- tests/_index.js | 1 + tests/adapter.replace.spec.ts | 108 ++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 tests/adapter.replace.spec.ts diff --git a/tests/_index.js b/tests/_index.js index 1ee1e5c4..84e39754 100644 --- a/tests/_index.js +++ b/tests/_index.js @@ -32,6 +32,7 @@ const testContext = require.context( //(adapter\.prepend)\.spec\.ts/ //(adapter\.reload)\.spec\.ts/ //(adapter\.remove)\.spec\.ts/ + //(adapter\.replace)\.spec\.ts/ //(adapter\.reset\-persistence)\.spec\.ts/ //(adapter\.reset)\.spec\.ts/ //(bug)\.spec\.ts/ diff --git a/tests/adapter.replace.spec.ts b/tests/adapter.replace.spec.ts new file mode 100644 index 00000000..142e318a --- /dev/null +++ b/tests/adapter.replace.spec.ts @@ -0,0 +1,108 @@ +import { makeTest, TestBedConfig } from './scaffolding/runner'; +import { generateItem, Item } from './miscellaneous/items'; +import { Misc } from './miscellaneous/misc'; +import { Settings, DevSettings, DatasourceGet } from '../src/component/interfaces/index'; + +const getDatasourceClass = (settings: Settings) => + class { + private data: Item[] = []; + + settings: Settings; + devSettings: DevSettings; + get: DatasourceGet; + + constructor() { + const min = settings.minIndex || 0; + const max = settings.maxIndex || 0; + + for (let i = min; i < min + max; ++i) { + this.data.push(generateItem(i)); + } + + this.settings = { ...settings }; + + this.get = (index: number, count: number, success: Function) => { + const data = []; + const start = index; + const end = start + count - 1; + if (start <= end) { + for (let i = start; i <= end; i++) { + const item = this.data.find(({ id }) => id === i); + if (!item) { + continue; + } + data.push(item); + } + } + success(data); + }; + } + + replaceOne(idToReplace: number, item: Item) { + const itemToReplace = this.data.find(({ id }) => id === idToReplace); + if (itemToReplace) { + Object.assign(itemToReplace, item); + } + // console.log('RUN', this.data); + } + }; + +const shouldReplace = (config: TestBedConfig) => (misc: Misc) => async (done: Function) => { + await misc.relaxNext(); + const { adapter } = misc; + const { custom: { indexToReplace }, datasourceSettings: { minIndex, itemSize } } = config; + const replaceOne = (misc.datasource as any).replaceOne.bind(misc.datasource); + const scrollPosition = (indexToReplace - 1 + minIndex - 1) * itemSize; + const newItem = generateItem(indexToReplace); + newItem.text += '*'; + + replaceOne(indexToReplace, newItem); + await adapter.remove(({ $index }) => $index === indexToReplace); + await adapter.insert({ + before: ({ $index }) => $index === indexToReplace, + items: [newItem] + }); + + adapter.fix({ scrollPosition }); + await misc.relaxNext(); + + expect(adapter.firstVisible.$index).toEqual(indexToReplace); + expect(misc.getElementText(indexToReplace)).toEqual(indexToReplace + ': ' + newItem.text); + done(); +}; + +describe('Adapter Replace Spec', () => { + + const baseSettings: Settings = { + startIndex: 1, + minIndex: 1, + maxIndex: 100, + adapter: true, + itemSize: 20 + }; + + const configList: TestBedConfig[] = [{ + datasourceSettings: { ...baseSettings }, + custom: { + indexToReplace: 3 // visible replace + } + }, { + datasourceSettings: { ...baseSettings }, + custom: { + indexToReplace: 50 // invisible replace + } + }]; + + configList.forEach(config => config.datasourceClass = getDatasourceClass(config.datasourceSettings)); + + describe('simple replacement', () => + configList.forEach(config => + makeTest({ + title: 'should work', + config, + it: shouldReplace(config) + }) + ) + ); + +}); From 100fcc47e0e1a04ecd1df1d938e18799e18704da Mon Sep 17 00:00:00 2001 From: Denis Hilt Date: Sat, 7 Nov 2020 21:41:50 +0300 Subject: [PATCH 2/7] issue-171 replace method and process stub --- src/component/classes/adapter.ts | 20 ++++++++++++---- src/component/inputs/adapter.ts | 16 +++++++++++++ src/component/interfaces/adapter.ts | 5 ++++ src/component/interfaces/index.ts | 2 ++ src/component/interfaces/process.ts | 1 + src/component/processes/adapter/replace.ts | 27 ++++++++++++++++++++++ src/component/processes/index.ts | 3 ++- src/component/workflow-transducer.ts | 9 ++++++++ 8 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 src/component/processes/adapter/replace.ts diff --git a/src/component/classes/adapter.ts b/src/component/classes/adapter.ts index 754059cc..d7d4f054 100644 --- a/src/component/classes/adapter.ts +++ b/src/component/classes/adapter.ts @@ -19,10 +19,11 @@ import { AdapterAppendOptions, AdapterClipOptions, AdapterInsertOptions, + AdapterReplaceOptions, AdapterFixOptions, ScrollerWorkflow, IDatasourceOptional, - ProcessSubject + ProcessSubject, } from '../interfaces/index'; const ADAPTER_PROPS_STUB = ADAPTER_PROPS(EMPTY_ITEM); @@ -109,9 +110,9 @@ export class Adapter implements IAdapter { // restore original values from the publicContext if present const adapterProps = publicContext ? ADAPTER_PROPS_STUB.map(prop => ({ - ...prop, - value: (publicContext as any)[prop.name] - })) + ...prop, + value: (publicContext as any)[prop.name] + })) : ADAPTER_PROPS(EMPTY_ITEM); // Scalar permanent props @@ -247,7 +248,7 @@ export class Adapter implements IAdapter { this.relax$.complete(); } Object.values(this.source).forEach(observable => observable.complete()); - } + } reset(options?: IDatasourceOptional): any { this.reloadCounter++; @@ -324,6 +325,15 @@ export class Adapter implements IAdapter { }); } + replace(options: AdapterReplaceOptions): any { + this.logger.logAdapterMethod('replace', options); + this.workflow.call({ + process: Process.replace, + status: ProcessStatus.start, + payload: options + }); + } + fix(options: AdapterFixOptions): any { this.logger.logAdapterMethod('fix', options); this.workflow.call({ diff --git a/src/component/inputs/adapter.ts b/src/component/inputs/adapter.ts index 693cc5b6..ec2d9c9c 100644 --- a/src/component/inputs/adapter.ts +++ b/src/component/inputs/adapter.ts @@ -106,6 +106,21 @@ const INSERT_METHOD_PARAMS: ICommonProps = { } }; +enum AdapterReplaceParams { + items = 'items', + predicate = 'predicate', +} + +const REPLACE_METHOD_PARAMS: ICommonProps = { + [AdapterInsertParams.items]: { + validators: [ITEM_LIST], + mandatory: true + }, + [AdapterReplaceParams.predicate]: { + validators: [FUNC_WITH_X_ARGUMENTS(1)] + } +}; + enum AdapterFixParams { scrollPosition = 'scrollPosition', minIndex = 'minIndex', @@ -151,5 +166,6 @@ export const ADAPTER_METHODS = { REMOVE: REMOVE_METHOD_PARAMS, CLIP: CLIP_METHOD_PARAMS, INSERT: INSERT_METHOD_PARAMS, + REPLACE: REPLACE_METHOD_PARAMS, FIX: FIX_METHOD_PARAMS, }; diff --git a/src/component/interfaces/adapter.ts b/src/component/interfaces/adapter.ts index 24d0dfeb..3e8016fc 100644 --- a/src/component/interfaces/adapter.ts +++ b/src/component/interfaces/adapter.ts @@ -51,6 +51,11 @@ export interface AdapterInsertOptions { decrease?: boolean; } +export interface AdapterReplaceOptions { + items: any[]; + predicate: ItemsPredicate; +} + export interface AdapterFixOptions { scrollPosition?: number; minIndex?: number; diff --git a/src/component/interfaces/index.ts b/src/component/interfaces/index.ts index 304b4398..7c041937 100644 --- a/src/component/interfaces/index.ts +++ b/src/component/interfaces/index.ts @@ -19,6 +19,7 @@ import { AdapterPrependOptions, AdapterClipOptions, AdapterInsertOptions, + AdapterReplaceOptions, AdapterFixOptions, AdapterMethodResult, IAdapter, @@ -60,6 +61,7 @@ export { AdapterPrependOptions, AdapterClipOptions, AdapterInsertOptions, + AdapterReplaceOptions, AdapterFixOptions, Settings, DevSettings, diff --git a/src/component/interfaces/process.ts b/src/component/interfaces/process.ts index 90973682..13b9a6f7 100644 --- a/src/component/interfaces/process.ts +++ b/src/component/interfaces/process.ts @@ -7,6 +7,7 @@ export enum Process { prepend = 'adapter.prepend', check = 'adapter.check', remove = 'adapter.remove', + replace = 'adapter.replace', userClip = 'adapter.clip', insert = 'adapter.insert', fix = 'adapter.fix', diff --git a/src/component/processes/adapter/replace.ts b/src/component/processes/adapter/replace.ts new file mode 100644 index 00000000..c31380cc --- /dev/null +++ b/src/component/processes/adapter/replace.ts @@ -0,0 +1,27 @@ +import { Scroller } from '../../scroller'; +import { ADAPTER_METHODS, validate } from '../../inputs/index'; +import { Process, ProcessStatus, AdapterReplaceOptions } from '../../interfaces/index'; + +export default class Replace { + + static process = Process.replace; + + static run(scroller: Scroller, options: AdapterReplaceOptions) { + const methodData = validate(options, ADAPTER_METHODS.REPLACE); + if (!methodData.isValid) { + scroller.logger.log(() => methodData.showErrors()); + scroller.workflow.call({ + process: Process.replace, + status: ProcessStatus.error, + payload: { error: `Wrong argument of the "Adapter.replace" method call` } + }); + return; + } + + scroller.workflow.call({ + process: Process.replace, + status: ProcessStatus.done + }); + } + +} diff --git a/src/component/processes/index.ts b/src/component/processes/index.ts index ce01afef..34a6d472 100644 --- a/src/component/processes/index.ts +++ b/src/component/processes/index.ts @@ -7,6 +7,7 @@ import Check from './adapter/check'; import Remove from './adapter/remove'; import UserClip from './adapter/clip'; import Insert from './adapter/insert'; +import Replace from './adapter/replace'; import Fix from './adapter/fix'; import Start from './start'; import PreFetch from './preFetch'; @@ -20,6 +21,6 @@ import End from './end'; export { Init, Scroll, - Reset, Reload, Append, Check, Remove, UserClip, Insert, Fix, + Reset, Reload, Append, Check, Remove, UserClip, Insert, Replace, Fix, Start, PreFetch, Fetch, PostFetch, Render, PreClip, Clip, Adjust, End }; diff --git a/src/component/workflow-transducer.ts b/src/component/workflow-transducer.ts index db914328..a456d973 100644 --- a/src/component/workflow-transducer.ts +++ b/src/component/workflow-transducer.ts @@ -8,6 +8,7 @@ import { Remove, UserClip, Insert, + Replace, Fix, Start, PreFetch, @@ -117,6 +118,14 @@ export const runStateMachine = ({ run(Init)(process); } break; + case Process.replace: + if (status === Status.start) { + run(Replace)(payload); + } + if (status === Status.next) { + run(Init)(process); + } + break; case Process.fix: if (status === Status.start) { run(Fix)(payload); From 6dc142241585ae54de7df6c75a895a119bd19e98 Mon Sep 17 00:00:00 2001 From: Denis Hilt Date: Tue, 1 Dec 2020 17:42:36 +0300 Subject: [PATCH 3/7] issue-171 base process --- src/component/classes/adapter.ts | 34 ++++---- src/component/classes/adapter/props.ts | 5 ++ src/component/classes/logger.ts | 8 +- src/component/inputs/adapter.ts | 40 ++++++---- src/component/interfaces/adapter.ts | 3 +- src/component/interfaces/index.ts | 5 +- src/component/interfaces/process.ts | 31 +++++--- src/component/processes/_base.ts | 9 +++ src/component/processes/adapter/_base.ts | 39 +++++++++ src/component/processes/adapter/append.ts | 35 ++++---- src/component/processes/adapter/check.ts | 9 +-- src/component/processes/adapter/clip.ts | 17 ++-- src/component/processes/adapter/fix.ts | 28 +++---- src/component/processes/adapter/insert.ts | 22 ++---- src/component/processes/adapter/reload.ts | 11 ++- src/component/processes/adapter/remove.ts | 26 ++---- src/component/processes/adapter/replace.ts | 20 ++--- src/component/processes/adapter/reset.ts | 33 +++----- src/component/processes/adjust.ts | 11 ++- src/component/processes/clip.ts | 14 ++-- src/component/processes/end.ts | 11 ++- src/component/processes/fetch.ts | 11 ++- src/component/processes/init.ts | 11 ++- src/component/processes/postFetch.ts | 11 ++- src/component/processes/preClip.ts | 9 +-- src/component/processes/preFetch.ts | 13 ++- src/component/processes/render.ts | 11 ++- src/component/processes/scroll.ts | 11 ++- src/component/processes/start.ts | 9 +-- src/component/workflow-transducer.ts | 92 ++++++++++------------ src/component/workflow.ts | 8 +- tests/adapter-promises.spec.ts | 11 --- tests/adapter.remove.spec.ts | 4 +- tests/adapter.replace.spec.ts | 51 ++++++------ tests/adapter.reset-persistence.spec.ts | 3 +- tests/adapter.reset.spec.ts | 4 +- tests/miscellaneous/misc.ts | 15 +++- 37 files changed, 347 insertions(+), 338 deletions(-) create mode 100644 src/component/processes/_base.ts create mode 100644 src/component/processes/adapter/_base.ts diff --git a/src/component/classes/adapter.ts b/src/component/classes/adapter.ts index c8f372ce..2b6a16f5 100644 --- a/src/component/classes/adapter.ts +++ b/src/component/classes/adapter.ts @@ -11,7 +11,7 @@ import { IAdapterProp, AdapterMethodResult, IAdapter, - Process, + AdapterProcess, ProcessStatus, ItemAdapter, ItemsPredicate, @@ -38,10 +38,10 @@ const fixScalarWanted = (name: string, container: { [key: string]: boolean }) => } }; -const convertAppendArgs = (isAppend: boolean, options: any, eof?: boolean) => { +const convertAppendArgs = (prepend: boolean, options: any, eof?: boolean) => { if (!(options !== null && typeof options === 'object' && options.hasOwnProperty('items'))) { const items = !Array.isArray(options) ? [options] : options; - options = isAppend ? { items, eof } : { items, bof: eof }; + options = prepend ? { items, bof: eof } : { items, eof }; } return options; }; @@ -265,7 +265,7 @@ export class Adapter implements IAdapter { this.reloadCounter++; this.logger.logAdapterMethod(`reset`, options, ` of ${this.reloadId}`); this.workflow.call({ - process: Process.reset, + process: AdapterProcess.reset, status: ProcessStatus.start, payload: options }); @@ -275,36 +275,36 @@ export class Adapter implements IAdapter { this.reloadCounter++; this.logger.logAdapterMethod(`reload`, options, ` of ${this.reloadId}`); this.workflow.call({ - process: Process.reload, + process: AdapterProcess.reload, status: ProcessStatus.start, payload: options }); } append(options: AdapterAppendOptions | any, eof?: boolean): any { - options = convertAppendArgs(true, options, eof); // support old signature + options = convertAppendArgs(false, options, eof); // support old signature this.logger.logAdapterMethod('append', [options.items, options.eof]); this.workflow.call({ - process: Process.append, + process: AdapterProcess.append, status: ProcessStatus.start, - payload: options + payload: { prepend: false, options } }); } prepend(options: AdapterPrependOptions | any, bof?: boolean): any { - options = convertAppendArgs(false, options, bof); // support old signature + options = convertAppendArgs(true, options, bof); // support old signature this.logger.logAdapterMethod('prepend', [options.items, options.bof]); this.workflow.call({ - process: Process.prepend, + process: AdapterProcess.append, status: ProcessStatus.start, - payload: options + payload: { prepend: true, options } }); } check(): any { this.logger.logAdapterMethod('check'); this.workflow.call({ - process: Process.check, + process: AdapterProcess.check, status: ProcessStatus.start }); } @@ -313,7 +313,7 @@ export class Adapter implements IAdapter { options = convertRemoveArgs(options); // support old signature this.logger.logAdapterMethod('remove', options); this.workflow.call({ - process: Process.remove, + process: AdapterProcess.remove, status: ProcessStatus.start, payload: options }); @@ -322,7 +322,7 @@ export class Adapter implements IAdapter { clip(options?: AdapterClipOptions): any { this.logger.logAdapterMethod('clip', options); this.workflow.call({ - process: Process.userClip, + process: AdapterProcess.clip, status: ProcessStatus.start, payload: options }); @@ -331,7 +331,7 @@ export class Adapter implements IAdapter { insert(options: AdapterInsertOptions): any { this.logger.logAdapterMethod('insert', options); this.workflow.call({ - process: Process.insert, + process: AdapterProcess.insert, status: ProcessStatus.start, payload: options }); @@ -340,7 +340,7 @@ export class Adapter implements IAdapter { replace(options: AdapterReplaceOptions): any { this.logger.logAdapterMethod('replace', options); this.workflow.call({ - process: Process.replace, + process: AdapterProcess.replace, status: ProcessStatus.start, payload: options }); @@ -349,7 +349,7 @@ export class Adapter implements IAdapter { fix(options: AdapterFixOptions): any { this.logger.logAdapterMethod('fix', options); this.workflow.call({ - process: Process.fix, + process: AdapterProcess.fix, status: ProcessStatus.start, payload: options }); diff --git a/src/component/classes/adapter/props.ts b/src/component/classes/adapter/props.ts index e24fdc8f..10eee5fb 100644 --- a/src/component/classes/adapter/props.ts +++ b/src/component/classes/adapter/props.ts @@ -107,6 +107,11 @@ export const ADAPTER_PROPS = (nullItem: any): IAdapterProp[] => [ name: 'insert', value: noop }, + { + type: Prop.WorkflowRunner, + name: 'replace', + value: noop + }, { type: Prop.WorkflowRunner, name: 'fix', diff --git a/src/component/classes/logger.ts b/src/component/classes/logger.ts index 69bd0832..82a44681 100644 --- a/src/component/classes/logger.ts +++ b/src/component/classes/logger.ts @@ -1,5 +1,5 @@ import { Scroller } from '../scroller'; -import { Process, ProcessStatus as Status, ProcessSubject } from '../interfaces/index'; +import { CommonProcess, AdapterProcess, ProcessStatus as Status, ProcessSubject } from '../interfaces/index'; type LogType = [any?, ...any[]]; @@ -112,15 +112,15 @@ export class Logger { // inner loop start-end log const loopLog: string[] = []; if ( - process === Process.init && status === Status.next + process === CommonProcess.init && status === Status.next ) { loopLog.push(`%c---=== loop ${this.getLoopIdNext()} start`); } else if ( - process === Process.end + process === CommonProcess.end ) { loopLog.push(`%c---=== loop ${this.getLoopId()} done`); const parent = payload && payload.process; - if (status === Status.next && (parent !== Process.reset && parent !== Process.reload)) { + if (status === Status.next && (parent !== AdapterProcess.reset && parent !== AdapterProcess.reload)) { loopLog[0] += `, loop ${this.getLoopIdNext()} start`; } } diff --git a/src/component/inputs/adapter.ts b/src/component/inputs/adapter.ts index 01e8768a..bfc3627d 100644 --- a/src/component/inputs/adapter.ts +++ b/src/component/inputs/adapter.ts @@ -1,6 +1,6 @@ import { VALIDATORS } from './validation'; import { DatasourceProps } from './datasource'; -import { ICommonProps } from '../interfaces/index'; +import { ICommonProps, AdapterProcessMap, AdapterProcess as Process } from '../interfaces/index'; const { INTEGER, @@ -15,6 +15,9 @@ const { OR, } = VALIDATORS; +enum AdapterNoParams { } +const NO_METHOD_PARAMS: ICommonProps = {}; + const RESET_METHOD_PARAMS: ICommonProps = { [DatasourceProps.get]: { validators: [FUNC_WITH_X_AND_MORE_ARGUMENTS(2)] @@ -159,21 +162,26 @@ const FIX_METHOD_PARAMS: ICommonProps = { }, }; -export const AdapterMethods = { - Reset: AdapterInsertParams, - Reload: AdapterReloadParams, - Clip: AdapterClipParams, - Insert: AdapterInsertParams, - Fix: AdapterFixParams, +export const AdapterMethods: AdapterProcessMap = { + [Process.reset]: DatasourceProps, + [Process.reload]: AdapterReloadParams, + [Process.append]: AdapterAppendParams, + [Process.check]: AdapterNoParams, + [Process.remove]: AdapterRemoveParams, + [Process.clip]: AdapterClipParams, + [Process.insert]: AdapterInsertParams, + [Process.replace]: AdapterReplaceParams, + [Process.fix]: AdapterFixParams, }; -export const ADAPTER_METHODS = { - RESET: RESET_METHOD_PARAMS, - RELOAD: RELOAD_METHOD_PARAMS, - APPEND: APPEND_METHOD_PARAMS, - REMOVE: REMOVE_METHOD_PARAMS, - CLIP: CLIP_METHOD_PARAMS, - INSERT: INSERT_METHOD_PARAMS, - REPLACE: REPLACE_METHOD_PARAMS, - FIX: FIX_METHOD_PARAMS, +export const ADAPTER_METHODS: AdapterProcessMap> = { + [Process.reset]: RESET_METHOD_PARAMS, + [Process.reload]: RELOAD_METHOD_PARAMS, + [Process.append]: APPEND_METHOD_PARAMS, + [Process.check]: NO_METHOD_PARAMS, + [Process.remove]: REMOVE_METHOD_PARAMS, + [Process.clip]: CLIP_METHOD_PARAMS, + [Process.insert]: INSERT_METHOD_PARAMS, + [Process.replace]: REPLACE_METHOD_PARAMS, + [Process.fix]: FIX_METHOD_PARAMS, }; diff --git a/src/component/interfaces/adapter.ts b/src/component/interfaces/adapter.ts index 10a323f9..b67321f0 100644 --- a/src/component/interfaces/adapter.ts +++ b/src/component/interfaces/adapter.ts @@ -105,7 +105,8 @@ export interface IAdapter { remove(args: AdapterRemoveOptions | ItemsPredicate): MethodResult; // + old signature clip(options?: AdapterClipOptions): MethodResult; insert(options: AdapterInsertOptions): MethodResult; + replace(options: AdapterReplaceOptions): MethodResult; fix(options: AdapterFixOptions): MethodResult; // experimental - relax(callback?: Function): MethodResult; // experimental + relax(callback?: Function): MethodResult; showLog(): void; } diff --git a/src/component/interfaces/index.ts b/src/component/interfaces/index.ts index c1fe4df0..f0205f39 100644 --- a/src/component/interfaces/index.ts +++ b/src/component/interfaces/index.ts @@ -28,7 +28,7 @@ import { import { Settings, DevSettings } from './settings'; import { Direction } from './direction'; import { ScrollEventData, ScrollState, State } from './state'; -import { Process, ProcessStatus, ProcessSubject } from './process'; +import { CommonProcess, AdapterProcess, Process, ProcessStatus, ProcessSubject, AdapterProcessMap } from './process'; import { ValidatorType, ValidatedValue, @@ -71,9 +71,12 @@ export { ScrollEventData, ScrollState, State, + CommonProcess, + AdapterProcess, Process, ProcessStatus, ProcessSubject, + AdapterProcessMap, ValidatorType, ValidatedValue, IValidator, diff --git a/src/component/interfaces/process.ts b/src/component/interfaces/process.ts index 13b9a6f7..7be9e51c 100644 --- a/src/component/interfaces/process.ts +++ b/src/component/interfaces/process.ts @@ -1,16 +1,6 @@ -export enum Process { +export enum CommonProcess { init = 'init', scroll = 'scroll', - reset = 'adapter.reset', - reload = 'adapter.reload', - append = 'adapter.append', - prepend = 'adapter.prepend', - check = 'adapter.check', - remove = 'adapter.remove', - replace = 'adapter.replace', - userClip = 'adapter.clip', - insert = 'adapter.insert', - fix = 'adapter.fix', start = 'start', preFetch = 'preFetch', fetch = 'fetch', @@ -19,9 +9,22 @@ export enum Process { preClip = 'preClip', clip = 'clip', adjust = 'adjust', - end = 'end' + end = 'end', +} +export enum AdapterProcess { + reset = 'adapter.reset', + reload = 'adapter.reload', + append = 'adapter.append', + check = 'adapter.check', + remove = 'adapter.remove', + replace = 'adapter.replace', + clip = 'adapter.clip', + insert = 'adapter.insert', + fix = 'adapter.fix', } +export type Process = CommonProcess | AdapterProcess; + export enum ProcessStatus { start = 'start', next = 'next', @@ -34,3 +37,7 @@ export interface ProcessSubject { status: ProcessStatus; payload?: any; } + +export type AdapterProcessMap = { + [key in AdapterProcess]: T; +}; diff --git a/src/component/processes/_base.ts b/src/component/processes/_base.ts new file mode 100644 index 00000000..e3ecf98a --- /dev/null +++ b/src/component/processes/_base.ts @@ -0,0 +1,9 @@ +import { Process } from '../interfaces/index'; + +export const getBaseProcess = (process: Process) => + + class BaseAdapterProcess { + + static process: Process = process; + + }; diff --git a/src/component/processes/adapter/_base.ts b/src/component/processes/adapter/_base.ts new file mode 100644 index 00000000..e7784df9 --- /dev/null +++ b/src/component/processes/adapter/_base.ts @@ -0,0 +1,39 @@ +import { getBaseProcess } from '../_base'; +import { Scroller } from '../../scroller'; +import { ADAPTER_METHODS, validate } from '../../inputs/index'; +import { AdapterProcess, IValidatedData, ProcessStatus } from '../../interfaces/index'; + +export interface IParseInput { + data: IValidatedData; + params?: T; +} + +export const getBaseAdapterProcess = (process: AdapterProcess) => + + class BaseAdapterProcess extends getBaseProcess(process) { + + static process: AdapterProcess = process; + + static parseInput(scroller: Scroller, options: T): IParseInput { + const inputData = validate(options, ADAPTER_METHODS[process]); + const result: IParseInput = { data: inputData }; + + if (inputData.isValid) { + result.params = Object.entries(inputData.params) + .reduce((acc, [key, { value }]) => ({ + ...acc, + [key]: value + }), {} as T); + } else { + scroller.logger.log(() => inputData.showErrors()); + scroller.workflow.call({ + process, + status: ProcessStatus.error, + payload: { error: `Wrong argument of the "${process}" method call` } + }); + } + + return result; + } + + }; diff --git a/src/component/processes/adapter/append.ts b/src/component/processes/adapter/append.ts index caaa697a..5430a0b3 100644 --- a/src/component/processes/adapter/append.ts +++ b/src/component/processes/adapter/append.ts @@ -1,45 +1,38 @@ +import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; -import { ADAPTER_METHODS, validate } from '../../inputs/index'; import { Item } from '../../classes/item'; -import { Process, ProcessStatus, AdapterAppendOptions } from '../../interfaces/index'; +import { AdapterProcess, ProcessStatus, AdapterAppendOptions, AdapterPrependOptions } from '../../interfaces/index'; -export default class Append { +type AdapterAppendPrependOptions = AdapterAppendOptions & AdapterPrependOptions; - static process = Process.append; +export default class Append extends getBaseAdapterProcess(AdapterProcess.append) { - static run(scroller: Scroller, process: Process, options: AdapterAppendOptions) { + static run(scroller: Scroller, { prepend, options }: { prepend: boolean, options: AdapterAppendPrependOptions }) { - const methodData = validate(options, ADAPTER_METHODS.APPEND); - if (!methodData.isValid) { - scroller.logger.log(() => methodData.showErrors()); - scroller.workflow.call({ - process, - status: ProcessStatus.error, - payload: { error: `Wrong argument of the "${process}" method call` } - }); + const { params } = Append.parseInput(scroller, options); + if (!params) { return; } - - const { items, bof, eof } = methodData.params; - const prepend = process === Process.prepend; - const _eof = !!(prepend ? bof.value : eof.value); + const { items, bof, eof } = params; + const _eof = !!(prepend ? bof : eof); // virtual prepend case: shift abs min index and update viewport params if ( (prepend && _eof && !scroller.buffer.bof) || (!prepend && _eof && !scroller.buffer.eof) ) { - Append.doVirtualize(scroller, items.value, prepend); + Append.doVirtualize(scroller, items, prepend); scroller.workflow.call({ - process, + process: Append.process, status: ProcessStatus.done }); return; } - Append.simulateFetch(scroller, items.value, _eof, prepend); + Append.simulateFetch(scroller, items, _eof, prepend); + scroller.workflow.call({ - process, + process: Append.process, status: ProcessStatus.next }); } diff --git a/src/component/processes/adapter/check.ts b/src/component/processes/adapter/check.ts index 89327780..ef89da7f 100644 --- a/src/component/processes/adapter/check.ts +++ b/src/component/processes/adapter/check.ts @@ -1,9 +1,8 @@ +import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; -import { Process, ProcessStatus, Direction } from '../../interfaces/index'; +import { AdapterProcess, ProcessStatus, Direction } from '../../interfaces/index'; -export default class Check { - - static process = Process.check; +export default class Check extends getBaseAdapterProcess(AdapterProcess.check) { static run(scroller: Scroller) { const { workflow, buffer, state: { fetch }, viewport } = scroller; @@ -36,7 +35,7 @@ export default class Check { scroller.logger.stat('check'); workflow.call({ - process: Process.check, + process: Check.process, status: Number.isFinite(min) ? ProcessStatus.next : ProcessStatus.done }); } diff --git a/src/component/processes/adapter/clip.ts b/src/component/processes/adapter/clip.ts index dde65772..3add5892 100644 --- a/src/component/processes/adapter/clip.ts +++ b/src/component/processes/adapter/clip.ts @@ -1,20 +1,17 @@ +import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; -import { ADAPTER_METHODS, validate } from '../../inputs/index'; -import { AdapterClipOptions, Process, ProcessStatus } from '../../interfaces/index'; +import { AdapterClipOptions, AdapterProcess, ProcessStatus } from '../../interfaces/index'; -export default class UserClip { - - static process = Process.userClip; +export default class UserClip extends getBaseAdapterProcess(AdapterProcess.clip) { static run(scroller: Scroller, options?: AdapterClipOptions) { - const methodData = validate(options, ADAPTER_METHODS.CLIP); - const { backwardOnly, forwardOnly } = methodData.params; + const { params } = UserClip.parseInput(scroller, options); - scroller.state.clip.forceForward = !backwardOnly.value; - scroller.state.clip.forceBackward = !forwardOnly.value; + scroller.state.clip.forceForward = !(params && params.backwardOnly); + scroller.state.clip.forceBackward = !(params && params.forwardOnly); scroller.workflow.call({ - process: Process.userClip, + process: UserClip.process, status: ProcessStatus.next }); } diff --git a/src/component/processes/adapter/fix.ts b/src/component/processes/adapter/fix.ts index 015e93df..a80869eb 100644 --- a/src/component/processes/adapter/fix.ts +++ b/src/component/processes/adapter/fix.ts @@ -1,7 +1,8 @@ +import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; -import { ADAPTER_METHODS, AdapterMethods, validate } from '../../inputs/index'; +import { AdapterMethods } from '../../inputs/index'; import { - Process, + AdapterProcess, ProcessStatus, ItemsPredicate, ItemsLooper, @@ -9,33 +10,26 @@ import { IValidatedData, } from '../../interfaces/index'; -const { Fix: FixParams } = AdapterMethods; +const { [AdapterProcess.fix]: FixParams } = AdapterMethods; -export default class Fix { - - static process = Process.fix; +export default class Fix extends getBaseAdapterProcess(AdapterProcess.fix) { static run(scroller: Scroller, options: AdapterFixOptions) { const { workflow } = scroller; - const methodData = validate(options, ADAPTER_METHODS.FIX); - if (!methodData.isValid) { - scroller.logger.log(() => methodData.showErrors()); - scroller.workflow.call({ - process: Process.fix, - status: ProcessStatus.error, - payload: { error: `Wrong argument of the "Adapter.fix" method call` } - }); + + const { data, params } = Fix.parseInput(scroller, options); + if (!params) { return; } - Object.entries(methodData.params).forEach(([key, value]) => { + Object.entries(data.params).forEach(([key, value]) => { if (value.isSet && value.isValid) { - Fix.runByType(scroller, key, value.value, methodData); + Fix.runByType(scroller, key, value.value, data); } }); workflow.call({ - process: Process.fix, + process: Fix.process, status: ProcessStatus.done }); } diff --git a/src/component/processes/adapter/insert.ts b/src/component/processes/adapter/insert.ts index 2a5874b3..3dbaa774 100644 --- a/src/component/processes/adapter/insert.ts +++ b/src/component/processes/adapter/insert.ts @@ -1,31 +1,23 @@ +import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; -import { ADAPTER_METHODS, validate } from '../../inputs/index'; import { Item } from '../../classes/item'; import { - Process, ProcessStatus, AdapterInsertOptions, ItemsPredicate, IValidatedData + AdapterProcess, ProcessStatus, AdapterInsertOptions, ItemsPredicate, IValidatedData } from '../../interfaces/index'; -export default class Insert { - - static process = Process.insert; +export default class Insert extends getBaseAdapterProcess(AdapterProcess.insert) { static run(scroller: Scroller, options: AdapterInsertOptions) { - const methodData = validate(options, ADAPTER_METHODS.INSERT); - if (!methodData.isValid) { - scroller.logger.log(() => methodData.showErrors()); - scroller.workflow.call({ - process: Process.insert, - status: ProcessStatus.error, - payload: { error: `Wrong argument of the "Adapter.insert" method call` } - }); + const { data } = Insert.parseInput(scroller, options); + if (!data.isValid) { return; } - const next = Insert.doInsert(scroller, methodData); + const next = Insert.doInsert(scroller, data); scroller.workflow.call({ - process: Process.insert, + process: Insert.process, status: next ? ProcessStatus.next : ProcessStatus.done }); } diff --git a/src/component/processes/adapter/reload.ts b/src/component/processes/adapter/reload.ts index b09ac104..c63bb498 100644 --- a/src/component/processes/adapter/reload.ts +++ b/src/component/processes/adapter/reload.ts @@ -1,9 +1,8 @@ +import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; -import { Process, ProcessStatus } from '../../interfaces/index'; +import { AdapterProcess, ProcessStatus } from '../../interfaces/index'; -export default class Reload { - - static process = Process.reload; +export default class Reload extends getBaseAdapterProcess(AdapterProcess.reload) { static run(scroller: Scroller, reloadIndex: any) { const { viewport, state, buffer } = scroller; @@ -17,11 +16,11 @@ export default class Reload { if (scroller.adapter.isLoading) { scroller.scrollCleanup(); payload.finalize = true; - state.cycle.interrupter = Process.reload; + state.cycle.interrupter = Reload.process; } scroller.workflow.call({ - process: Process.reload, + process: Reload.process, status: ProcessStatus.next, payload }); diff --git a/src/component/processes/adapter/remove.ts b/src/component/processes/adapter/remove.ts index 7b96087a..ea8d6cb6 100644 --- a/src/component/processes/adapter/remove.ts +++ b/src/component/processes/adapter/remove.ts @@ -1,27 +1,15 @@ +import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; -import { ADAPTER_METHODS, validate } from '../../inputs/index'; -import { Direction, AdapterRemoveOptions, ItemsPredicate, Process, ProcessStatus } from '../../interfaces/index'; +import { Direction, AdapterRemoveOptions, ItemsPredicate, AdapterProcess, ProcessStatus } from '../../interfaces/index'; -export default class Remove { - - static process = Process.remove; +export default class Remove extends getBaseAdapterProcess(AdapterProcess.remove) { static run(scroller: Scroller, options: AdapterRemoveOptions) { - const methodData = validate(options, ADAPTER_METHODS.REMOVE); - if (!methodData.isValid) { - scroller.logger.log(() => methodData.showErrors()); - scroller.workflow.call({ - process: Process.remove, - status: ProcessStatus.error, - payload: { error: `Wrong argument of the "Adapter.remove" method call` } - }); + + const { params } = Remove.parseInput(scroller, options); + if (!params) { return; } - const params: AdapterRemoveOptions = { - predicate: methodData.params.predicate.value, - indexes: methodData.params.indexes.value, - increase: methodData.params.increase.value - }; const shouldRemove = Remove.removeBufferedItems(scroller, params); const shouldRemoveVirtual = Remove.removeVirtualItems(scroller, params); @@ -38,7 +26,7 @@ export default class Remove { } scroller.workflow.call({ - process: Process.remove, + process: Remove.process, status: shouldRemove || shouldRemoveVirtual ? ProcessStatus.next : ProcessStatus.done }); } diff --git a/src/component/processes/adapter/replace.ts b/src/component/processes/adapter/replace.ts index c31380cc..7cfed4b3 100644 --- a/src/component/processes/adapter/replace.ts +++ b/src/component/processes/adapter/replace.ts @@ -1,25 +1,17 @@ +import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; -import { ADAPTER_METHODS, validate } from '../../inputs/index'; -import { Process, ProcessStatus, AdapterReplaceOptions } from '../../interfaces/index'; +import { AdapterProcess, ProcessStatus, AdapterReplaceOptions } from '../../interfaces/index'; -export default class Replace { - - static process = Process.replace; +export default class Replace extends getBaseAdapterProcess(AdapterProcess.replace) { static run(scroller: Scroller, options: AdapterReplaceOptions) { - const methodData = validate(options, ADAPTER_METHODS.REPLACE); - if (!methodData.isValid) { - scroller.logger.log(() => methodData.showErrors()); - scroller.workflow.call({ - process: Process.replace, - status: ProcessStatus.error, - payload: { error: `Wrong argument of the "Adapter.replace" method call` } - }); + const { params } = Replace.parseInput(scroller, options); + if (!params) { return; } scroller.workflow.call({ - process: Process.replace, + process: Replace.process, status: ProcessStatus.done }); } diff --git a/src/component/processes/adapter/reset.ts b/src/component/processes/adapter/reset.ts index 0158c7f1..efa8d590 100644 --- a/src/component/processes/adapter/reset.ts +++ b/src/component/processes/adapter/reset.ts @@ -1,29 +1,22 @@ +import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; -import { ADAPTER_METHODS, validate } from '../../inputs/index'; +import { ADAPTER_METHODS } from '../../inputs/index'; import { Datasource } from '../../classes/datasource'; -import { Process, ProcessStatus, IDatasourceOptional } from '../../interfaces/index'; +import { AdapterProcess, ProcessStatus, IDatasourceOptional } from '../../interfaces/index'; -export default class Reset { +export default class Reset extends getBaseAdapterProcess(AdapterProcess.reset) { - static process = Process.reset; - - static run(scroller: Scroller, params?: IDatasourceOptional) { + static run(scroller: Scroller, options?: IDatasourceOptional) { const { datasource, buffer, viewport: { paddings }, state } = scroller; - if (params) { - const methodData = validate(params, ADAPTER_METHODS.RESET); - if (!methodData.isValid) { - scroller.logger.log(() => methodData.showErrors()); - scroller.workflow.call({ - process: Process.reset, - status: ProcessStatus.error, - payload: { error: `Wrong argument of the "Adapter.reset" method call` } - }); + if (options) { + const { data } = Reset.parseInput(scroller, options); + if (!data.isValid) { return; } - const constructed = params instanceof Datasource; - Object.keys(ADAPTER_METHODS.RESET).forEach(key => { - const param = methodData.params[key]; + const constructed = options instanceof Datasource; + Object.keys(ADAPTER_METHODS[Reset.process]).forEach(key => { + const param = data.params[key]; if (param.isSet || (constructed && datasource.hasOwnProperty(key))) { (datasource as any)[key] = param.value; } @@ -37,11 +30,11 @@ export default class Reset { const payload: any = { datasource }; if (scroller.adapter.isLoading) { payload.finalize = true; - state.cycle.interrupter = Process.reset; + state.cycle.interrupter = Reset.process; } scroller.workflow.call({ - process: Process.reset, + process: Reset.process, status: ProcessStatus.next, payload }); diff --git a/src/component/processes/adjust.ts b/src/component/processes/adjust.ts index 62968a19..09008fe6 100644 --- a/src/component/processes/adjust.ts +++ b/src/component/processes/adjust.ts @@ -1,9 +1,8 @@ +import { getBaseProcess } from './_base'; import { Scroller } from '../scroller'; -import { Process, ProcessStatus } from '../interfaces/index'; +import { CommonProcess, ProcessStatus } from '../interfaces/index'; -export default class Adjust { - - static process = Process.adjust; +export default class Adjust extends getBaseProcess(CommonProcess.adjust) { static run(scroller: Scroller) { const { workflow, viewport, state: { scrollState } } = scroller; @@ -12,7 +11,7 @@ export default class Adjust { // padding-elements adjustments if (!Adjust.setPaddings(scroller)) { return workflow.call({ - process: Process.adjust, + process: Adjust.process, status: ProcessStatus.error, payload: { error: `Can't get visible item` } }); @@ -25,7 +24,7 @@ export default class Adjust { // set new position using animation frame Adjust.setPosition(scroller, position, () => workflow.call({ - process: Process.adjust, + process: Adjust.process, status: ProcessStatus.done }) ); diff --git a/src/component/processes/clip.ts b/src/component/processes/clip.ts index 52e20f85..7b320cf4 100644 --- a/src/component/processes/clip.ts +++ b/src/component/processes/clip.ts @@ -1,18 +1,18 @@ +import { getBaseProcess } from './_base'; import { Scroller } from '../scroller'; -import { Direction, Process, ProcessStatus } from '../interfaces/index'; +import { Direction, CommonProcess, ProcessStatus } from '../interfaces/index'; -export default class Clip { - - static process = Process.clip; +export default class Clip extends getBaseProcess(CommonProcess.clip) { static run(scroller: Scroller) { - const { workflow } = scroller; + const { workflow, state } = scroller; Clip.doClip(scroller); + workflow.call({ - process: Process.clip, + process: Clip.process, status: ProcessStatus.next, - payload: { process: scroller.state.cycle.initiator } + payload: { process: state.cycle.initiator } }); } diff --git a/src/component/processes/end.ts b/src/component/processes/end.ts index 6d3c2309..0e98951f 100644 --- a/src/component/processes/end.ts +++ b/src/component/processes/end.ts @@ -1,12 +1,11 @@ +import { getBaseProcess } from './_base'; import { Scroller } from '../scroller'; import { EMPTY_ITEM } from '../classes/adapter/context'; -import { Process, ProcessStatus, Direction, ScrollerWorkflow } from '../interfaces/index'; +import { CommonProcess, ProcessStatus, Direction, ScrollerWorkflow } from '../interfaces/index'; const isInterrupted = ({ call }: ScrollerWorkflow): boolean => !!call.interrupted; -export default class End { - - static process = Process.end; +export default class End extends getBaseProcess(CommonProcess.end) { static run(scroller: Scroller, { error }: any = {}) { const { workflow, state: { cycle: { interrupter } } } = scroller; @@ -18,14 +17,14 @@ export default class End { // explicit interruption for we don't want to go through the inner loop finalizing if (isInterrupted(workflow)) { - workflow.call({ process: Process.end, status: ProcessStatus.done }); + workflow.call({ process: End.process, status: ProcessStatus.done }); return; } const next = End.finalizeInnerLoop(scroller, error); workflow.call({ - process: Process.end, + process: End.process, status: next ? ProcessStatus.next : ProcessStatus.done, payload: { ...(interrupter ? { process: interrupter } : {}) } }); diff --git a/src/component/processes/fetch.ts b/src/component/processes/fetch.ts index 4681975d..17abc56d 100644 --- a/src/component/processes/fetch.ts +++ b/src/component/processes/fetch.ts @@ -1,11 +1,10 @@ import { Observable, Observer } from 'rxjs'; +import { getBaseProcess } from './_base'; import { Scroller } from '../scroller'; -import { Process, ProcessStatus } from '../interfaces/index'; +import { CommonProcess, ProcessStatus } from '../interfaces/index'; -export default class Fetch { - - static process = Process.fetch; +export default class Fetch extends getBaseProcess(CommonProcess.fetch) { static run(scroller: Scroller) { const { workflow } = scroller; @@ -17,14 +16,14 @@ export default class Fetch { ); scroller.state.fetch.newItemsData = data; workflow.call({ - process: Process.fetch, + process: Fetch.process, status: ProcessStatus.next }); } function fail(error: string) { workflow.call({ - process: Process.fetch, + process: Fetch.process, status: ProcessStatus.error, payload: { error } }); diff --git a/src/component/processes/init.ts b/src/component/processes/init.ts index 4213dcbe..857154ae 100644 --- a/src/component/processes/init.ts +++ b/src/component/processes/init.ts @@ -1,11 +1,10 @@ +import { getBaseProcess } from './_base'; import { Scroller } from '../scroller'; -import { Process, ProcessStatus } from '../interfaces/index'; +import { CommonProcess, AdapterProcess, Process, ProcessStatus } from '../interfaces/index'; -const initProcesses = [Process.init, Process.reset, Process.reload]; +const initProcesses = [CommonProcess.init, AdapterProcess.reset, AdapterProcess.reload]; -export default class Init { - - static process = Process.init; +export default class Init extends getBaseProcess(CommonProcess.init) { static run(scroller: Scroller, process: Process) { const { state, workflow, adapter } = scroller; @@ -14,7 +13,7 @@ export default class Init { state.cycle.start(isInitial, process); adapter.isLoading = true; workflow.call({ - process: Process.init, + process: Init.process, status: ProcessStatus.next }); } diff --git a/src/component/processes/postFetch.ts b/src/component/processes/postFetch.ts index ba9a7982..f54a62d0 100644 --- a/src/component/processes/postFetch.ts +++ b/src/component/processes/postFetch.ts @@ -1,24 +1,23 @@ +import { getBaseProcess } from './_base'; import { Scroller } from '../scroller'; import { Item } from '../classes/item'; -import { Process, ProcessStatus } from '../interfaces/index'; +import { CommonProcess, ProcessStatus } from '../interfaces/index'; -export default class PostFetch { - - static process = Process.postFetch; +export default class PostFetch extends getBaseProcess(CommonProcess.postFetch) { static run(scroller: Scroller) { const { workflow } = scroller; if (PostFetch.setItems(scroller)) { PostFetch.setBufferLimits(scroller); workflow.call({ - process: Process.postFetch, + process: PostFetch.process, status: scroller.state.fetch.hasNewItems ? ProcessStatus.next : ProcessStatus.done }); } else { workflow.call({ - process: Process.postFetch, + process: PostFetch.process, status: ProcessStatus.error, payload: { error: `Can't set buffer items` } }); diff --git a/src/component/processes/preClip.ts b/src/component/processes/preClip.ts index 70b61d0f..c7a5b8cd 100644 --- a/src/component/processes/preClip.ts +++ b/src/component/processes/preClip.ts @@ -1,15 +1,14 @@ +import { getBaseProcess } from './_base'; import { Scroller } from '../scroller'; -import { Direction, Process, ProcessStatus } from '../interfaces/index'; +import { Direction, CommonProcess, ProcessStatus } from '../interfaces/index'; -export default class PreClip { - - static process = Process.preClip; +export default class PreClip extends getBaseProcess(CommonProcess.preClip) { static run(scroller: Scroller) { PreClip.prepareClip(scroller); scroller.workflow.call({ - process: Process.preClip, + process: PreClip.process, status: ProcessStatus.next, payload: { doClip: scroller.state.clip.doClip diff --git a/src/component/processes/preFetch.ts b/src/component/processes/preFetch.ts index aca4345a..18c5ebc5 100644 --- a/src/component/processes/preFetch.ts +++ b/src/component/processes/preFetch.ts @@ -1,9 +1,8 @@ +import { getBaseProcess } from './_base'; import { Scroller } from '../scroller'; -import { Process, ProcessStatus, Direction } from '../interfaces/index'; +import { CommonProcess, AdapterProcess, ProcessStatus, Direction } from '../interfaces/index'; -export default class PreFetch { - - static process = Process.preFetch; +export default class PreFetch extends getBaseProcess(CommonProcess.preFetch) { static run(scroller: Scroller) { const { workflow, buffer, state: { fetch, cycle } } = scroller; @@ -27,7 +26,7 @@ export default class PreFetch { PreFetch.setFetchDirection(scroller); workflow.call({ - process: Process.preFetch, + process: PreFetch.process, status: PreFetch.getStatus(scroller), payload: { process: cycle.initiator } }); @@ -242,8 +241,8 @@ export default class PreFetch { static getStatus(scroller: Scroller): ProcessStatus { const { cycle, fetch } = scroller.state; - if (cycle.initiator === Process.userClip) { - scroller.logger.log(() => `going to skip fetch due to "${Process.userClip}" process`); + if (cycle.initiator === AdapterProcess.clip) { + scroller.logger.log(() => `going to skip fetch due to "${AdapterProcess.clip}" process`); return ProcessStatus.next; } if (fetch.shouldFetch) { diff --git a/src/component/processes/render.ts b/src/component/processes/render.ts index 94bdb568..77e641c5 100644 --- a/src/component/processes/render.ts +++ b/src/component/processes/render.ts @@ -1,10 +1,9 @@ +import { getBaseProcess } from './_base'; import { Scroller } from '../scroller'; import { Item } from '../classes/item'; -import { Process, ProcessStatus } from '../interfaces/index'; +import { CommonProcess, ProcessStatus } from '../interfaces/index'; -export default class Render { - - static process = Process.render; +export default class Render extends getBaseProcess(CommonProcess.render) { static run(scroller: Scroller) { const { workflow, state: { cycle, render, scrollState }, viewport } = scroller; @@ -16,13 +15,13 @@ export default class Render { render.renderTimer = null; if (Render.processElements(scroller)) { workflow.call({ - process: Process.render, + process: Render.process, status: render.noSize ? ProcessStatus.done : ProcessStatus.next, payload: { process: cycle.initiator } }); } else { workflow.call({ - process: Process.render, + process: Render.process, status: ProcessStatus.error, payload: { error: `Can't associate item with element` } }); diff --git a/src/component/processes/scroll.ts b/src/component/processes/scroll.ts index 0d913ea5..14c11f88 100644 --- a/src/component/processes/scroll.ts +++ b/src/component/processes/scroll.ts @@ -1,11 +1,10 @@ +import { getBaseProcess } from './_base'; import { Scroller } from '../scroller'; -import { Direction, Process, ProcessStatus, ScrollEventData, ScrollerWorkflow } from '../interfaces/index'; +import { Direction, CommonProcess, ProcessStatus, ScrollEventData, ScrollerWorkflow } from '../interfaces/index'; -export default class Scroll { +export default class Scroll extends getBaseProcess(CommonProcess.scroll) { - static process = Process.scroll; - - static run(scroller: Scroller, process: Process, payload?: { event?: Event }) { + static run(scroller: Scroller, payload?: { event?: Event }) { const { workflow, viewport } = scroller; const position = viewport.scrollPosition; @@ -102,7 +101,7 @@ export default class Scroll { } workflow.call({ - process: Process.scroll, + process: Scroll.process, status: ProcessStatus.next }); } diff --git a/src/component/processes/start.ts b/src/component/processes/start.ts index aa1ba738..4e13368b 100644 --- a/src/component/processes/start.ts +++ b/src/component/processes/start.ts @@ -1,9 +1,8 @@ +import { getBaseProcess } from './_base'; import { Scroller } from '../scroller'; -import { Process, ProcessStatus } from '../interfaces/index'; +import { CommonProcess, ProcessStatus } from '../interfaces/index'; -export default class Start { - - static process = Process.start; +export default class Start extends getBaseProcess(CommonProcess.start) { static run(scroller: Scroller) { const { state: { cycle, scrollState, fetch, clip, render }, adapter } = scroller; @@ -19,7 +18,7 @@ export default class Start { render.reset(); scroller.workflow.call({ - process: Process.start, + process: Start.process, status: ProcessStatus.next, payload: { ...(cycle.innerLoop.first ? { process: cycle.initiator } : {}) } }); diff --git a/src/component/workflow-transducer.ts b/src/component/workflow-transducer.ts index fa51361f..f611528a 100644 --- a/src/component/workflow-transducer.ts +++ b/src/component/workflow-transducer.ts @@ -22,7 +22,8 @@ import { } from './processes/index'; import { - Process, + CommonProcess, + AdapterProcess, ProcessStatus as Status, StateMachineParams } from './interfaces/index'; @@ -39,7 +40,7 @@ export const runStateMachine = ({ return; } switch (process) { - case Process.init: + case CommonProcess.init: if (status === Status.start) { // App start run(Init)(process); } @@ -47,19 +48,22 @@ export const runStateMachine = ({ run(Start)(); } break; - case Process.scroll: + case CommonProcess.scroll: if (status === Status.start) { - run(Scroll)(process, payload); + run(Scroll)(payload); } if (status === Status.next) { run(Init)(process); } break; - case Process.reset: - case Process.reload: - const processToRun = process === Process.reset ? Reset : Reload; - if (status === Status.start) { // Adapter reload/reset - run(processToRun)(payload); + case AdapterProcess.reset: + case AdapterProcess.reload: + if (status === Status.start) { + if (process === AdapterProcess.reset) { + run(Reset)(payload); + } else { + run(Reload)(payload); + } } if (status === Status.next) { interrupt({ process, ...payload }); @@ -70,23 +74,15 @@ export const runStateMachine = ({ } } break; - case Process.append: - if (status === Status.start) { - run(Append)(process, payload); - } - if (status === Status.next) { - run(Init)(process); - } - break; - case Process.prepend: + case AdapterProcess.append: if (status === Status.start) { - run(Append)(process, payload); + run(Append)(payload); } if (status === Status.next) { run(Init)(process); } break; - case Process.check: + case AdapterProcess.check: if (status === Status.start) { run(Check)(); } @@ -94,7 +90,7 @@ export const runStateMachine = ({ run(Init)(process); } break; - case Process.remove: + case AdapterProcess.remove: if (status === Status.start) { run(Remove)(payload); } @@ -102,7 +98,7 @@ export const runStateMachine = ({ run(Init)(process); } break; - case Process.userClip: + case AdapterProcess.clip: if (status === Status.start) { run(UserClip)(payload); } @@ -110,7 +106,7 @@ export const runStateMachine = ({ run(Init)(process); } break; - case Process.insert: + case AdapterProcess.insert: if (status === Status.start) { run(Insert)(payload); } @@ -118,7 +114,7 @@ export const runStateMachine = ({ run(Init)(process); } break; - case Process.replace: + case AdapterProcess.replace: if (status === Status.start) { run(Replace)(payload); } @@ -126,7 +122,7 @@ export const runStateMachine = ({ run(Init)(process); } break; - case Process.fix: + case AdapterProcess.fix: if (status === Status.start) { run(Fix)(payload); } @@ -134,28 +130,27 @@ export const runStateMachine = ({ run(Init)(process); } break; - case Process.start: + case CommonProcess.start: switch (payload.process) { - case Process.append: - case Process.prepend: - case Process.check: - case Process.insert: + case AdapterProcess.append: + case AdapterProcess.check: + case AdapterProcess.insert: run(Render)(); break; - case Process.remove: + case AdapterProcess.remove: run(Clip)(); break; - case Process.userClip: + case AdapterProcess.clip: run(PreFetch)(); break; default: run(PreFetch)(); } break; - case Process.preFetch: + case CommonProcess.preFetch: if (status === Status.next) { switch (payload.process) { - case Process.userClip: + case AdapterProcess.clip: run(PreClip)(); break; default: @@ -166,10 +161,10 @@ export const runStateMachine = ({ run(End)(); } break; - case Process.fetch: + case CommonProcess.fetch: run(PostFetch)(); break; - case Process.postFetch: + case CommonProcess.postFetch: if (status === Status.next) { run(Render)(); } @@ -177,14 +172,13 @@ export const runStateMachine = ({ run(End)(); } break; - case Process.render: + case CommonProcess.render: if (status === Status.next) { switch (payload.process) { - case Process.append: - case Process.prepend: - case Process.check: - case Process.insert: - case Process.remove: + case AdapterProcess.append: + case AdapterProcess.check: + case AdapterProcess.insert: + case AdapterProcess.remove: run(Adjust)(); break; default: @@ -195,30 +189,30 @@ export const runStateMachine = ({ run(End)(); } break; - case Process.preClip: + case CommonProcess.preClip: if (payload.doClip) { run(Clip)(); } else { run(Adjust)(); } break; - case Process.clip: + case CommonProcess.clip: switch (payload.process) { - case Process.remove: + case AdapterProcess.remove: run(End)(); break; default: run(Adjust)(); } break; - case Process.adjust: + case CommonProcess.adjust: run(End)(); break; - case Process.end: + case CommonProcess.end: if (status === Status.next) { switch (payload.process) { - case Process.reset: - case Process.reload: + case AdapterProcess.reset: + case AdapterProcess.reload: done(); run(Init)(payload.process); break; diff --git a/src/component/workflow.ts b/src/component/workflow.ts index 80a2d088..addb11d3 100644 --- a/src/component/workflow.ts +++ b/src/component/workflow.ts @@ -5,12 +5,12 @@ import { Scroller } from './scroller'; import { runStateMachine } from './workflow-transducer'; import { IDatasource, + CommonProcess, Process, ProcessStatus as Status, ProcessSubject, ProcessStatus, WorkflowError, - ScrollerWorkflow, InterruptParams, StateMachineMethods } from './interfaces/index'; @@ -32,7 +32,7 @@ export class Workflow { this.isInitialized = false; this.dispose$ = new Subject(); this.process$ = new BehaviorSubject({ - process: Process.init, + process: CommonProcess.init, status: Status.start } as ProcessSubject); this.propagateChanges = run; @@ -78,7 +78,7 @@ export class Workflow { const { scrollEventReceiver } = this.scroller.viewport; const onScrollHandler: EventListener = event => this.callWorkflow({ - process: Process.scroll, + process: CommonProcess.scroll, status: ProcessStatus.start, payload: { event } }); @@ -105,7 +105,7 @@ export class Workflow { } this.scroller.logger.logProcess(data); - if (process === Process.end) { + if (process === CommonProcess.end) { this.scroller.finalize(); } runStateMachine({ diff --git a/tests/adapter-promises.spec.ts b/tests/adapter-promises.spec.ts index d50ef087..1b563cd2 100644 --- a/tests/adapter-promises.spec.ts +++ b/tests/adapter-promises.spec.ts @@ -199,17 +199,6 @@ const immediateConfigErrorList = [{ async: false, error: true } as ICustom -}, { - ...configBase, - custom: { - method: 'prepend', - options: { - items: 'error' - }, - newWFCycle: false, - async: false, - error: true - } as ICustom }, { ...configBase, custom: { diff --git a/tests/adapter.remove.spec.ts b/tests/adapter.remove.spec.ts index d51a5205..c656187c 100644 --- a/tests/adapter.remove.spec.ts +++ b/tests/adapter.remove.spec.ts @@ -1,7 +1,7 @@ import { makeTest, TestBedConfig } from './scaffolding/runner'; import { Misc } from './miscellaneous/misc'; import { removeItems } from './miscellaneous/items'; -import { Process } from '../src/component/interfaces/index'; +import { AdapterProcess } from '../src/component/interfaces/index'; const baseConfig: TestBedConfig = { datasourceName: 'limited--99-100-processor', @@ -183,7 +183,7 @@ const shouldBreak = (config: TestBedConfig) => (misc: Misc) => (done: Function) expect(misc.workflow.cyclesDone).toEqual(1); expect(misc.innerLoopCount).toEqual(innerLoopCount); expect(misc.workflow.errors.length).toEqual(1); - expect(misc.workflow.errors[0].process).toEqual(Process.remove); + expect(misc.workflow.errors[0].process).toEqual(AdapterProcess.remove); done(); }, 40); } diff --git a/tests/adapter.replace.spec.ts b/tests/adapter.replace.spec.ts index 142e318a..0d37d847 100644 --- a/tests/adapter.replace.spec.ts +++ b/tests/adapter.replace.spec.ts @@ -3,6 +3,26 @@ import { generateItem, Item } from './miscellaneous/items'; import { Misc } from './miscellaneous/misc'; import { Settings, DevSettings, DatasourceGet } from '../src/component/interfaces/index'; +const baseSettings: Settings = { + startIndex: 1, + minIndex: 1, + maxIndex: 100, + adapter: true, + itemSize: 20 +}; + +const configList: TestBedConfig[] = [{ + datasourceSettings: { ...baseSettings }, + custom: { + indexToReplace: 3 // visible replace + } +}, { + datasourceSettings: { ...baseSettings }, + custom: { + indexToReplace: 50 // invisible replace + } +}]; + const getDatasourceClass = (settings: Settings) => class { private data: Item[] = []; @@ -47,6 +67,8 @@ const getDatasourceClass = (settings: Settings) => } }; +configList.forEach(config => config.datasourceClass = getDatasourceClass(config.datasourceSettings)); + const shouldReplace = (config: TestBedConfig) => (misc: Misc) => async (done: Function) => { await misc.relaxNext(); const { adapter } = misc; @@ -62,7 +84,12 @@ const shouldReplace = (config: TestBedConfig) => (misc: Misc) => async (done: Fu before: ({ $index }) => $index === indexToReplace, items: [newItem] }); + // await adapter.replace({ + // predicate: ({ $index }) => $index === indexToReplace, + // items: [newItem] + // }); + await misc.scrollMinMax(); adapter.fix({ scrollPosition }); await misc.relaxNext(); @@ -73,28 +100,6 @@ const shouldReplace = (config: TestBedConfig) => (misc: Misc) => async (done: Fu describe('Adapter Replace Spec', () => { - const baseSettings: Settings = { - startIndex: 1, - minIndex: 1, - maxIndex: 100, - adapter: true, - itemSize: 20 - }; - - const configList: TestBedConfig[] = [{ - datasourceSettings: { ...baseSettings }, - custom: { - indexToReplace: 3 // visible replace - } - }, { - datasourceSettings: { ...baseSettings }, - custom: { - indexToReplace: 50 // invisible replace - } - }]; - - configList.forEach(config => config.datasourceClass = getDatasourceClass(config.datasourceSettings)); - describe('simple replacement', () => configList.forEach(config => makeTest({ @@ -105,4 +110,4 @@ describe('Adapter Replace Spec', () => { ) ); -}); +}); diff --git a/tests/adapter.reset-persistence.spec.ts b/tests/adapter.reset-persistence.spec.ts index bc3ae727..1b7086f2 100644 --- a/tests/adapter.reset-persistence.spec.ts +++ b/tests/adapter.reset-persistence.spec.ts @@ -2,12 +2,11 @@ import { Subscription } from 'rxjs'; import { Datasource } from '../src/component/classes/datasource'; import { ADAPTER_PROPS } from '../src/component/classes/adapter/props'; -import { Process, IDatasourceOptional, Direction, IAdapter, IDatasource } from '../src/component/interfaces'; +import { IDatasourceOptional, IAdapter, IDatasource } from '../src/component/interfaces'; import { makeTest, TestBedConfig } from './scaffolding/runner'; import { datasourceStore } from './scaffolding/datasources'; import { Misc } from './miscellaneous/misc'; -import { generateItem } from './miscellaneous/items'; const ADAPTER_PROPS_STUB = ADAPTER_PROPS(null); diff --git a/tests/adapter.reset.spec.ts b/tests/adapter.reset.spec.ts index d52ae917..0bc06ed0 100644 --- a/tests/adapter.reset.spec.ts +++ b/tests/adapter.reset.spec.ts @@ -1,4 +1,4 @@ -import { Process, IDatasourceOptional, Direction } from '../src/component/interfaces'; +import { AdapterProcess, IDatasourceOptional, Direction } from '../src/component/interfaces'; import { makeTest, TestBedConfig } from './scaffolding/runner'; import { datasourceStore } from './scaffolding/datasources'; import { Misc } from './miscellaneous/misc'; @@ -181,7 +181,7 @@ const shouldReset = (config: TestBedConfig, fail?: boolean) => (misc: Misc) => ( } else { doReset(config, misc); if (fail) { - expect(misc.workflow.errors.some(e => e.process === Process.reset)).toEqual(true); + expect(misc.workflow.errors.some(e => e.process === AdapterProcess.reset)).toEqual(true); done(); } } diff --git a/tests/miscellaneous/misc.ts b/tests/miscellaneous/misc.ts index fbe09c34..4c1533da 100644 --- a/tests/miscellaneous/misc.ts +++ b/tests/miscellaneous/misc.ts @@ -175,7 +175,20 @@ export class Misc { await this.relaxNext(); return this.scrollDownRecursively(); } - return; + } + + async scrollMinMax(): Promise { + if (this.getScrollPosition() !== 0) { + this.scrollMin(); + await this.relaxNext(); + this.scrollMax(); + await this.relaxNext(); + } else { + this.scrollMax(); + await this.relaxNext(); + this.scrollMin(); + await this.relaxNext(); + } } delay(ms: number): Promise { From ead77aa5601025de62595dc996dee8c6bedf1197 Mon Sep 17 00:00:00 2001 From: Denis Hilt Date: Tue, 1 Dec 2020 21:42:59 +0300 Subject: [PATCH 4/7] issue-171 use Remove inside Replace --- src/component/processes/adapter/remove.ts | 40 ++++++++++++++-------- src/component/processes/adapter/replace.ts | 10 ++++++ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/component/processes/adapter/remove.ts b/src/component/processes/adapter/remove.ts index ea8d6cb6..d199eb6b 100644 --- a/src/component/processes/adapter/remove.ts +++ b/src/component/processes/adapter/remove.ts @@ -11,8 +11,17 @@ export default class Remove extends getBaseAdapterProcess(AdapterProcess.remove) return; } + const shouldRemove = Remove.removeItems(scroller, params); + + scroller.workflow.call({ + process: Remove.process, + status: shouldRemove ? ProcessStatus.next : ProcessStatus.done + }); + } + + static removeItems(scroller: Scroller, params: AdapterRemoveOptions, sequenceOnly = false): boolean { const shouldRemove = Remove.removeBufferedItems(scroller, params); - const shouldRemoveVirtual = Remove.removeVirtualItems(scroller, params); + const shouldRemoveVirtual = Remove.removeVirtualItems(scroller, params, sequenceOnly); if (shouldRemove || shouldRemoveVirtual) { const { clip } = scroller.state; @@ -25,10 +34,7 @@ export default class Remove extends getBaseAdapterProcess(AdapterProcess.remove) } } - scroller.workflow.call({ - process: Remove.process, - status: shouldRemove || shouldRemoveVirtual ? ProcessStatus.next : ProcessStatus.done - }); + return shouldRemove || shouldRemoveVirtual; } static removeBufferedItems(scroller: Scroller, options: AdapterRemoveOptions): boolean { @@ -68,26 +74,32 @@ export default class Remove extends getBaseAdapterProcess(AdapterProcess.remove) return result; } - static removeVirtualItems(scroller: Scroller, options: AdapterRemoveOptions): boolean { - const { indexes } = options; - let result = false; + static removeVirtualItems(scroller: Scroller, { indexes }: AdapterRemoveOptions, sequenceOnly: boolean): boolean { if (!indexes) { return false; } + let last = null; const { state: { clip } } = scroller; const { finiteAbsMinIndex, firstIndex, finiteAbsMaxIndex, lastIndex } = scroller.buffer; - for (let i = indexes.length - 1; i >= 0; i--) { + for (let i = 0, len = indexes.length; i < len; i++) { + let dir = null; const index = indexes[i]; if (index >= finiteAbsMinIndex && firstIndex !== null && index < firstIndex) { - clip.virtual.backward.push(index); - result = true; + dir = Direction.backward; } if (index <= finiteAbsMaxIndex && lastIndex !== null && index > lastIndex) { - clip.virtual.forward.push(index); - result = true; + dir = Direction.forward; + } + if (dir !== null) { + if (sequenceOnly && last !== null && Math.abs(last - index) > 1) { + // allow only first strict uninterrupted sequence + break; + } + clip.virtual[dir].push(index); + last = index; } } - return result; + return last !== null; } } diff --git a/src/component/processes/adapter/replace.ts b/src/component/processes/adapter/replace.ts index 7cfed4b3..a46ee7d4 100644 --- a/src/component/processes/adapter/replace.ts +++ b/src/component/processes/adapter/replace.ts @@ -1,6 +1,7 @@ import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; import { AdapterProcess, ProcessStatus, AdapterReplaceOptions } from '../../interfaces/index'; +import Remove from './remove'; export default class Replace extends getBaseAdapterProcess(AdapterProcess.replace) { @@ -10,6 +11,15 @@ export default class Replace extends getBaseAdapterProcess(AdapterProcess.replac return; } + const shouldRemove = Remove.removeItems(scroller, params, true); + if (!shouldRemove) { + scroller.logger.log(() => 'no items to replace (not found)'); + return scroller.workflow.call({ + process: Replace.process, + status: ProcessStatus.done + }); + } + scroller.workflow.call({ process: Replace.process, status: ProcessStatus.done From b1a4ecb6fc9b497406e5d98b16d4aba48c505b41 Mon Sep 17 00:00:00 2001 From: Denis Hilt Date: Tue, 1 Dec 2020 21:43:22 +0300 Subject: [PATCH 5/7] issue-171 Adapter.remove skip spec --- tests/adapter.remove.spec.ts | 64 +++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/tests/adapter.remove.spec.ts b/tests/adapter.remove.spec.ts index c656187c..9e5ccbc8 100644 --- a/tests/adapter.remove.spec.ts +++ b/tests/adapter.remove.spec.ts @@ -1,7 +1,7 @@ import { makeTest, TestBedConfig } from './scaffolding/runner'; import { Misc } from './miscellaneous/misc'; import { removeItems } from './miscellaneous/items'; -import { AdapterProcess } from '../src/component/interfaces/index'; +import { AdapterProcess, ItemsPredicate } from '../src/component/interfaces/index'; const baseConfig: TestBedConfig = { datasourceName: 'limited--99-100-processor', @@ -56,6 +56,14 @@ const configListIndexes = [ } })); +const configListEmpty = [{ + ...configList[0], + custom: { ...configList[0].custom, predicate: (({ $index }) => $index > 999) as ItemsPredicate } +}, { + ...configList[1], + custom: { ...configList[1].custom, indexes: [999] } +}]; + const configListBad = [{ ...configList[0], custom: { ...configList[0].custom, predicate: null } @@ -173,21 +181,31 @@ const shouldRemove = (config: TestBedConfig, byId = false) => (misc: Misc) => as done(); }; -const shouldBreak = (config: TestBedConfig) => (misc: Misc) => (done: Function) => { - spyOn(misc.workflow, 'finalize').and.callFake(() => { - if (misc.workflow.cyclesDone === 1) { - const innerLoopCount = misc.innerLoopCount; - // call remove with wrong predicate - misc.adapter.remove({ predicate: config.custom.predicate }); - setTimeout(() => { - expect(misc.workflow.cyclesDone).toEqual(1); - expect(misc.innerLoopCount).toEqual(innerLoopCount); - expect(misc.workflow.errors.length).toEqual(1); - expect(misc.workflow.errors[0].process).toEqual(AdapterProcess.remove); - done(); - }, 40); - } - }); +const shouldSkip = (config: TestBedConfig) => (misc: Misc) => async (done: Function) => { + await misc.relaxNext(); + const innerLoopCount = misc.innerLoopCount; + const { predicate, indexes } = config.custom; + if (predicate) { + await misc.adapter.remove({ predicate }); + } else if (indexes) { + await misc.adapter.remove({ indexes }); + } + expect(misc.workflow.cyclesDone).toEqual(1); + expect(misc.innerLoopCount).toEqual(innerLoopCount); + expect(misc.workflow.errors.length).toEqual(0); + done(); +}; + +const shouldBreak = (config: TestBedConfig) => (misc: Misc) => async (done: Function) => { + await misc.relaxNext(); + const innerLoopCount = misc.innerLoopCount; + // call remove with wrong predicate + await misc.adapter.remove({ predicate: config.custom.predicate }); + expect(misc.workflow.cyclesDone).toEqual(1); + expect(misc.innerLoopCount).toEqual(innerLoopCount); + expect(misc.workflow.errors.length).toEqual(1); + expect(misc.workflow.errors[0].process).toEqual(AdapterProcess.remove); + done(); }; const shouldRemoveOutOfViewFixed = (config: TestBedConfig) => (misc: Misc) => async (done: Function) => { @@ -295,15 +313,23 @@ describe('Adapter Remove Spec', () => { ); }); - describe('Wrong', () => + describe('Empty', () => { + configListEmpty.forEach(config => + makeTest({ + config, + title: `should not remove due to empty ${config.custom.predicate ? 'predicate' : 'indexes'}`, + it: shouldSkip(config) + }) + ); + configListBad.forEach(config => makeTest({ config, title: 'should break due to wrong predicate', it: shouldBreak(config) }) - ) - ); + ); + }); describe('Virtual', () => { configListOutFixed.forEach(config => From 799dc20588a9d7f906eee3a9755bf303d94659ae Mon Sep 17 00:00:00 2001 From: Denis Hilt Date: Wed, 2 Dec 2020 01:40:54 +0300 Subject: [PATCH 6/7] issue-171 Replace process flow (single replace works) --- src/component/inputs/adapter.ts | 3 ++- src/component/processes/adapter/insert.ts | 20 +++++++++----------- src/component/processes/adapter/remove.ts | 4 ++-- src/component/processes/adapter/replace.ts | 20 +++++++++++++++++--- src/component/workflow-transducer.ts | 4 ++++ tests/adapter.remove.spec.ts | 7 ++++--- tests/adapter.replace.spec.ts | 11 +++-------- 7 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/component/inputs/adapter.ts b/src/component/inputs/adapter.ts index bfc3627d..29653f12 100644 --- a/src/component/inputs/adapter.ts +++ b/src/component/inputs/adapter.ts @@ -113,7 +113,8 @@ const INSERT_METHOD_PARAMS: ICommonProps = { validators: [FUNC_WITH_X_ARGUMENTS(1), ONE_OF_MUST([AdapterInsertParams.before])] }, [AdapterInsertParams.decrease]: { - validators: [BOOLEAN] + validators: [BOOLEAN], + defaultValue: false }, }; diff --git a/src/component/processes/adapter/insert.ts b/src/component/processes/adapter/insert.ts index 3dbaa774..6cd5b806 100644 --- a/src/component/processes/adapter/insert.ts +++ b/src/component/processes/adapter/insert.ts @@ -2,36 +2,34 @@ import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; import { Item } from '../../classes/item'; import { - AdapterProcess, ProcessStatus, AdapterInsertOptions, ItemsPredicate, IValidatedData + AdapterProcess, ProcessStatus, AdapterInsertOptions, ItemsPredicate } from '../../interfaces/index'; export default class Insert extends getBaseAdapterProcess(AdapterProcess.insert) { static run(scroller: Scroller, options: AdapterInsertOptions) { - const { data } = Insert.parseInput(scroller, options); - if (!data.isValid) { + const { params } = Insert.parseInput(scroller, options); + if (!params) { return; } - const next = Insert.doInsert(scroller, data); + const shouldInsert = Insert.doInsert(scroller, params); scroller.workflow.call({ process: Insert.process, - status: next ? ProcessStatus.next : ProcessStatus.done + status: shouldInsert ? ProcessStatus.next : ProcessStatus.done }); } - static doInsert(scroller: Scroller, { params }: IValidatedData): boolean { - const { buffer } = scroller; + static doInsert(scroller: Scroller, params: AdapterInsertOptions): boolean { const { before, after, items, decrease } = params; - const method: ItemsPredicate = before.isSet ? before.value : after.value; - const found = buffer.items.find(item => method(item.get())); + const method = (before || after) as ItemsPredicate; + const found = scroller.buffer.items.find(item => method(item.get())); if (!found) { return false; } - const decrement = decrease.isSet && decrease.value; - return Insert.simulateFetch(scroller, found, items.value, before.isSet, decrement); + return Insert.simulateFetch(scroller, found, items, !!before, !!decrease); } static simulateFetch( diff --git a/src/component/processes/adapter/remove.ts b/src/component/processes/adapter/remove.ts index d199eb6b..60137788 100644 --- a/src/component/processes/adapter/remove.ts +++ b/src/component/processes/adapter/remove.ts @@ -11,7 +11,7 @@ export default class Remove extends getBaseAdapterProcess(AdapterProcess.remove) return; } - const shouldRemove = Remove.removeItems(scroller, params); + const shouldRemove = Remove.doRemove(scroller, params); scroller.workflow.call({ process: Remove.process, @@ -19,7 +19,7 @@ export default class Remove extends getBaseAdapterProcess(AdapterProcess.remove) }); } - static removeItems(scroller: Scroller, params: AdapterRemoveOptions, sequenceOnly = false): boolean { + static doRemove(scroller: Scroller, params: AdapterRemoveOptions, sequenceOnly = false): boolean { const shouldRemove = Remove.removeBufferedItems(scroller, params); const shouldRemoveVirtual = Remove.removeVirtualItems(scroller, params, sequenceOnly); diff --git a/src/component/processes/adapter/replace.ts b/src/component/processes/adapter/replace.ts index a46ee7d4..0a4751df 100644 --- a/src/component/processes/adapter/replace.ts +++ b/src/component/processes/adapter/replace.ts @@ -1,7 +1,8 @@ import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; -import { AdapterProcess, ProcessStatus, AdapterReplaceOptions } from '../../interfaces/index'; +import { AdapterProcess, ProcessStatus, AdapterReplaceOptions, AdapterInsertOptions } from '../../interfaces/index'; import Remove from './remove'; +import Insert from './insert'; export default class Replace extends getBaseAdapterProcess(AdapterProcess.replace) { @@ -11,7 +12,7 @@ export default class Replace extends getBaseAdapterProcess(AdapterProcess.replac return; } - const shouldRemove = Remove.removeItems(scroller, params, true); + const shouldRemove = Remove.doRemove(scroller, params, true); if (!shouldRemove) { scroller.logger.log(() => 'no items to replace (not found)'); return scroller.workflow.call({ @@ -20,9 +21,22 @@ export default class Replace extends getBaseAdapterProcess(AdapterProcess.replac }); } + const insertOptions: AdapterInsertOptions = { + items: params.items, + before: params.predicate, + decrease: false + }; + const shouldInsert = Insert.doInsert(scroller, insertOptions); + if (!shouldInsert) { + return scroller.workflow.call({ + process: Replace.process, + status: ProcessStatus.done + }); + } + scroller.workflow.call({ process: Replace.process, - status: ProcessStatus.done + status: ProcessStatus.next }); } diff --git a/src/component/workflow-transducer.ts b/src/component/workflow-transducer.ts index f611528a..9895a4db 100644 --- a/src/component/workflow-transducer.ts +++ b/src/component/workflow-transducer.ts @@ -135,6 +135,7 @@ export const runStateMachine = ({ case AdapterProcess.append: case AdapterProcess.check: case AdapterProcess.insert: + case AdapterProcess.replace: run(Render)(); break; case AdapterProcess.remove: @@ -181,6 +182,9 @@ export const runStateMachine = ({ case AdapterProcess.remove: run(Adjust)(); break; + case AdapterProcess.replace: + run(Clip)(); + break; default: run(PreClip)(); } diff --git a/tests/adapter.remove.spec.ts b/tests/adapter.remove.spec.ts index 9e5ccbc8..becf24b0 100644 --- a/tests/adapter.remove.spec.ts +++ b/tests/adapter.remove.spec.ts @@ -145,9 +145,8 @@ const doRemove = async (config: TestBedConfig, misc: Misc, byId = false) => { const shouldRemove = (config: TestBedConfig, byId = false) => (misc: Misc) => async (done: Function) => { await misc.relaxNext(); - const bufferSizeBeforeRemove = misc.scroller.buffer.size; + const { size: bufferSizeBeforeRemove, minIndex, maxIndex, absMinIndex, absMaxIndex } = misc.scroller.buffer; const { remove: indexList, increase } = config.custom; - const { minIndex, maxIndex } = misc.scroller.buffer; const viewportSizeBeforeRemove = misc.getScrollableSize(); const sizeToRemove = indexList.length * misc.getItemSize(); const deltaSize = viewportSizeBeforeRemove - sizeToRemove; @@ -155,10 +154,12 @@ const shouldRemove = (config: TestBedConfig, byId = false) => (misc: Misc) => as const loopPendingSub = misc.adapter.loopPending$.subscribe(loopPending => { if (!loopPending) { // when the first loop after the Remove is done const len = indexList.length; - const { minIndex: min, maxIndex: max, size } = misc.scroller.buffer; + const { size, minIndex: min, maxIndex: max, absMinIndex: absMin, absMaxIndex: absMax } = misc.scroller.buffer; expect(size).toEqual(bufferSizeBeforeRemove - len); expect(min).toBe(minIndex + (increase ? len : 0)); expect(max).toBe(maxIndex - (increase ? 0 : len)); + expect(absMin).toBe(absMinIndex + (increase ? len : 0)); + expect(absMax).toBe(absMaxIndex - (increase ? 0 : len)); expect(deltaSize).toEqual(misc.getScrollableSize()); loopPendingSub.unsubscribe(); } diff --git a/tests/adapter.replace.spec.ts b/tests/adapter.replace.spec.ts index 0d37d847..6ce527e9 100644 --- a/tests/adapter.replace.spec.ts +++ b/tests/adapter.replace.spec.ts @@ -63,7 +63,6 @@ const getDatasourceClass = (settings: Settings) => if (itemToReplace) { Object.assign(itemToReplace, item); } - // console.log('RUN', this.data); } }; @@ -79,15 +78,11 @@ const shouldReplace = (config: TestBedConfig) => (misc: Misc) => async (done: Fu newItem.text += '*'; replaceOne(indexToReplace, newItem); - await adapter.remove(({ $index }) => $index === indexToReplace); - await adapter.insert({ - before: ({ $index }) => $index === indexToReplace, + + await adapter.replace({ + predicate: ({ $index }) => $index === indexToReplace, items: [newItem] }); - // await adapter.replace({ - // predicate: ({ $index }) => $index === indexToReplace, - // items: [newItem] - // }); await misc.scrollMinMax(); adapter.fix({ scrollPosition }); From 5c38ee4028c348bdda0e70e60e67ac2ab6bed129 Mon Sep 17 00:00:00 2001 From: Denis Hilt Date: Sat, 5 Dec 2020 06:24:55 +0300 Subject: [PATCH 7/7] issue-171 single replace spec --- src/component/processes/adapter/_base.ts | 11 ++++--- src/component/workflow-transducer.ts | 3 -- tests/adapter.replace.spec.ts | 40 ++++++++++++++++++------ tests/miscellaneous/misc.ts | 4 +++ 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/component/processes/adapter/_base.ts b/src/component/processes/adapter/_base.ts index e7784df9..706d0da9 100644 --- a/src/component/processes/adapter/_base.ts +++ b/src/component/processes/adapter/_base.ts @@ -15,17 +15,18 @@ export const getBaseAdapterProcess = (process: AdapterProcess) => static process: AdapterProcess = process; static parseInput(scroller: Scroller, options: T): IParseInput { - const inputData = validate(options, ADAPTER_METHODS[process]); - const result: IParseInput = { data: inputData }; + const result: IParseInput = { + data: validate(options, ADAPTER_METHODS[process]) + }; - if (inputData.isValid) { - result.params = Object.entries(inputData.params) + if (result.data.isValid) { + result.params = Object.entries(result.data.params) .reduce((acc, [key, { value }]) => ({ ...acc, [key]: value }), {} as T); } else { - scroller.logger.log(() => inputData.showErrors()); + scroller.logger.log(() => result.data.showErrors()); scroller.workflow.call({ process, status: ProcessStatus.error, diff --git a/src/component/workflow-transducer.ts b/src/component/workflow-transducer.ts index 9895a4db..2f702816 100644 --- a/src/component/workflow-transducer.ts +++ b/src/component/workflow-transducer.ts @@ -141,9 +141,6 @@ export const runStateMachine = ({ case AdapterProcess.remove: run(Clip)(); break; - case AdapterProcess.clip: - run(PreFetch)(); - break; default: run(PreFetch)(); } diff --git a/tests/adapter.replace.spec.ts b/tests/adapter.replace.spec.ts index 6ce527e9..8ebe15c6 100644 --- a/tests/adapter.replace.spec.ts +++ b/tests/adapter.replace.spec.ts @@ -3,7 +3,7 @@ import { generateItem, Item } from './miscellaneous/items'; import { Misc } from './miscellaneous/misc'; import { Settings, DevSettings, DatasourceGet } from '../src/component/interfaces/index'; -const baseSettings: Settings = { +const baseSettings = { startIndex: 1, minIndex: 1, maxIndex: 100, @@ -14,12 +14,20 @@ const baseSettings: Settings = { const configList: TestBedConfig[] = [{ datasourceSettings: { ...baseSettings }, custom: { - indexToReplace: 3 // visible replace + indexToReplace: baseSettings.minIndex + 1, + token: 'middle' } }, { datasourceSettings: { ...baseSettings }, custom: { - indexToReplace: 50 // invisible replace + indexToReplace: baseSettings.minIndex, + token: 'first' + } +}, { + datasourceSettings: { ...baseSettings, startIndex: baseSettings.maxIndex }, + custom: { + indexToReplace: baseSettings.maxIndex, + token: 'last' } }]; @@ -40,6 +48,7 @@ const getDatasourceClass = (settings: Settings) => } this.settings = { ...settings }; + // this.devSettings = { debug: true, logProcessRun: true }; this.get = (index: number, count: number, success: Function) => { const data = []; @@ -71,34 +80,45 @@ configList.forEach(config => config.datasourceClass = getDatasourceClass(config. const shouldReplace = (config: TestBedConfig) => (misc: Misc) => async (done: Function) => { await misc.relaxNext(); const { adapter } = misc; - const { custom: { indexToReplace }, datasourceSettings: { minIndex, itemSize } } = config; + const { custom: { indexToReplace, token }, datasourceSettings: { minIndex, itemSize } } = config; const replaceOne = (misc.datasource as any).replaceOne.bind(misc.datasource); - const scrollPosition = (indexToReplace - 1 + minIndex - 1) * itemSize; + const maxScrollPosition = misc.getMaxScrollPosition(); + const position = token === 'last' ? maxScrollPosition : (indexToReplace - 1 + minIndex - 1) * itemSize; const newItem = generateItem(indexToReplace); newItem.text += '*'; + // replace at the Datasource level replaceOne(indexToReplace, newItem); + // replace at the Viewport level (Adapter) await adapter.replace({ predicate: ({ $index }) => $index === indexToReplace, items: [newItem] }); await misc.scrollMinMax(); - adapter.fix({ scrollPosition }); - await misc.relaxNext(); - expect(adapter.firstVisible.$index).toEqual(indexToReplace); + // scroll to replaced item + if (misc.getScrollPosition() !== position) { + adapter.fix({ scrollPosition: position }); + await misc.relaxNext(); + } + + if (token === 'last') { + expect(adapter.lastVisible.$index).toEqual(indexToReplace); + } else { + expect(adapter.firstVisible.$index).toEqual(indexToReplace); + } expect(misc.getElementText(indexToReplace)).toEqual(indexToReplace + ': ' + newItem.text); done(); }; describe('Adapter Replace Spec', () => { - describe('simple replacement', () => + describe('single replacement', () => configList.forEach(config => makeTest({ - title: 'should work', + title: `should work (${config.custom.token})`, config, it: shouldReplace(config) }) diff --git a/tests/miscellaneous/misc.ts b/tests/miscellaneous/misc.ts index 4c1533da..a1f48e90 100644 --- a/tests/miscellaneous/misc.ts +++ b/tests/miscellaneous/misc.ts @@ -136,6 +136,10 @@ export class Misc { return this.getScrollableElement()[this.horizontal ? 'scrollLeft' : 'scrollTop']; } + getMaxScrollPosition(): number { + return this.getScrollableSize() - this.getViewportSize(); + } + scrollTo(value: number, native?: boolean) { if (native) { this.getScrollableElement()[this.horizontal ? 'scrollLeft' : 'scrollTop'] = value;