diff --git a/.eslintrc.js b/.eslintrc.js index 4e15650..5e7bdfe 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -39,6 +39,7 @@ module.exports = { 'no-empty': ['error', { allowEmptyCatch: true }], 'no-cond-assign': ['error', 'except-parens'], camelcase: ['error', { allow: ['^__', '^UNSAFE_'] }], + 'prefer-destructuring': 'off', 'consistent-return': 'off', 'import/prefer-default-export': 'off', 'import/no-extraneous-dependencies': 'off', diff --git a/jest.config.base.js b/jest.config.base.js index a9779da..942e96d 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -9,6 +9,7 @@ const testServerLoaderPath = path.resolve(__dirname, './config/jest-test-server. // https://jestjs.io/docs/en/configuration.html const base = { + cache: false, clearMocks: true, collectCoverage: true, coverageDirectory: './.coverage', diff --git a/packages/overlayscrollbars/src/options/options.ts b/packages/overlayscrollbars/src/options/options.ts index 60a9266..21483ee 100644 --- a/packages/overlayscrollbars/src/options/options.ts +++ b/packages/overlayscrollbars/src/options/options.ts @@ -1,10 +1,9 @@ import { optionsTemplateTypes as oTypes, - transform, - OptionsTemplate, + transformOptions, OptionsTemplateValue, - OptionsAndOptionsTemplateValue, - OptionsAndOptionsTemplate, + OptionsWithOptionsTemplateValue, + OptionsWithOptionsTemplate, Func, } from 'support/options'; import { ResizeBehavior, OverflowBehavior, VisibilityBehavior, AutoHideBehavior, Options } from 'options'; @@ -13,9 +12,9 @@ const classNameAllowedValues: OptionsTemplateValue = [oTypes.stri const numberAllowedValues: OptionsTemplateValue = oTypes.number; const booleanNullAllowedValues: OptionsTemplateValue = [oTypes.boolean, oTypes.null]; const stringArrayNullAllowedValues: OptionsTemplateValue | null> = [oTypes.string, oTypes.array, oTypes.null]; -const booleanTrueTemplate: OptionsAndOptionsTemplateValue = [true, oTypes.boolean]; -const booleanFalseTemplate: OptionsAndOptionsTemplateValue = [false, oTypes.boolean]; -const callbackTemplate: OptionsAndOptionsTemplateValue = [null, [oTypes.function, oTypes.null]]; +const booleanTrueTemplate: OptionsWithOptionsTemplateValue = [true, oTypes.boolean]; +const booleanFalseTemplate: OptionsWithOptionsTemplateValue = [false, oTypes.boolean]; +const callbackTemplate: OptionsWithOptionsTemplateValue = [null, [oTypes.function, oTypes.null]]; const resizeAllowedValues: OptionsTemplateValue = 'none both horizontal vertical'; const overflowBehaviorAllowedValues: OptionsTemplateValue = 'visible-hidden visible-scroll scroll hidden'; const scrollbarsVisibilityAllowedValues: OptionsTemplateValue = 'visible hidden auto'; @@ -36,7 +35,7 @@ const scrollbarsAutoHideAllowedValues: OptionsTemplateValue = * Property "a" has a default value of 'default' and it can be a string or null * Property "b" has a default value of 250 and it can be number */ -const defaultOptionsWithTemplate: OptionsAndOptionsTemplate> = { +const defaultOptionsWithTemplate: OptionsWithOptionsTemplate> = { className: ['os-theme-dark', classNameAllowedValues], // null || string resize: ['none', resizeAllowedValues], // none || both || horizontal || vertical || n || b || h || v sizeAutoCapable: booleanTrueTemplate, // true || false @@ -84,5 +83,4 @@ const defaultOptionsWithTemplate: OptionsAndOptionsTemplate> = }, }; -export const optionsTemplate: OptionsTemplate> = transform(defaultOptionsWithTemplate, true); -export const defaultOptions: Options = transform(defaultOptionsWithTemplate); +export const { _template: optionsTemplate, _options: defaultOptions } = transformOptions(defaultOptionsWithTemplate); diff --git a/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts b/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts index 01720df..8312535 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts @@ -1,4 +1,4 @@ -import { validate, assignDeep } from 'support'; +import { validateOptions, assignDeep } from 'support'; import { Options, optionsTemplate } from 'options'; import { TargetElement } from 'overlayscrollbars'; import { Environment } from 'environment'; @@ -32,11 +32,11 @@ export class OverlayScrollbars { #instanceVars: OverlayScrollbarsInstanceVars = { _setOptions(newOptions: Options): Options { const { _currentOptions } = this; - const { validated } = validate(newOptions, optionsTemplate, _currentOptions, true); + const { _validated } = validateOptions(newOptions, optionsTemplate, _currentOptions, true); - this._currentOptions = assignDeep({}, _currentOptions, validated); + this._currentOptions = assignDeep({}, _currentOptions, _validated); - return validated; + return _validated; }, }; diff --git a/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/StructureLifecycle.ts b/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/StructureLifecycle.ts index f8920ef..4defdee 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/StructureLifecycle.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/StructureLifecycle.ts @@ -10,6 +10,11 @@ import { TRBL, equalTRBL, createCache, + optionsTemplateTypes as oTypes, + transformOptions, + validateOptions, + OptionsTemplateValue, + assignDeep, } from 'support'; import { Lifecycle } from 'overlayscrollbars/lifecycles'; import { getEnvironment, Environment } from 'environment'; @@ -18,17 +23,25 @@ import { createTrinsicObserver } from 'overlayscrollbars/observers/TrinsicObserv export type OverflowBehavior = 'hidden' | 'scroll' | 'visible-hidden' | 'visible-scroll'; export interface StructureLifecycleOptions { - _paddingAbsolute?: boolean; - _overflowBehavior?: { - x: OverflowBehavior; - y: OverflowBehavior; + paddingAbsolute?: boolean; + overflowBehavior?: { + x?: OverflowBehavior; + y?: OverflowBehavior; }; } - interface StructureLifecycleCache { padding: TRBL; } +const overflowBehaviorAllowedValues: OptionsTemplateValue = 'visible-hidden visible-scroll scroll hidden'; +const { _template: optionsTemplate, _options: defaultOptions } = transformOptions>({ + paddingAbsolute: [false, oTypes.boolean], + overflowBehavior: { + x: ['scroll', overflowBehaviorAllowedValues], + y: ['scroll', overflowBehaviorAllowedValues], + }, +}); + const classNameHost = 'os-host'; const classNameViewport = 'os-viewport'; const classNameContent = 'os-content'; @@ -37,7 +50,13 @@ const classNameViewportScrollbarStyling = `${classNameViewport}-scrollbar-styled const cssMarginEnd = cssProperty('margin-inline-end'); const cssBorderEnd = cssProperty('border-inline-end'); -export const createStructureLifecycle = (target: HTMLElement, options?: StructureLifecycleOptions): Lifecycle => { +export const createStructureLifecycle = (target: HTMLElement, initialOptions?: StructureLifecycleOptions): Lifecycle => { + const options: Required = assignDeep( + {}, + defaultOptions, + validateOptions(initialOptions || {}, optionsTemplate)._validated + ); + const destructFns: (() => any)[] = []; const env: Environment = getEnvironment(); const scrollbarsOverlaid = env._nativeScrollbarIsOverlaid; @@ -69,9 +88,13 @@ export const createStructureLifecycle = (target: HTMLElement, options?: Structur destructFns.push(createTrinsicObserver(target, onTrinsicChanged)); return { - _options() { + _options(newOptions?: StructureLifecycleOptions) { // eslint-disable-next-line - console.log('_options'); + console.log('_options', newOptions); + }, + _update(force?: boolean) { + // eslint-disable-next-line + console.log('_options', force); }, _destruct() { runEach(destructFns); diff --git a/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/index.ts b/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/index.ts index a11e8ca..96eb28e 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/index.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/index.ts @@ -2,5 +2,6 @@ import { PlainObject } from 'typings'; export interface Lifecycle { _options(options?: T): void; + _update(force?: boolean): void; _destruct(): void; } diff --git a/packages/overlayscrollbars/src/support/cache/cache.ts b/packages/overlayscrollbars/src/support/cache/cache.ts index caa08bc..8930d5f 100644 --- a/packages/overlayscrollbars/src/support/cache/cache.ts +++ b/packages/overlayscrollbars/src/support/cache/cache.ts @@ -19,7 +19,7 @@ export type CacheUpdateFunction = (current?: T[P], previou export type CacheEqualFunction = (a?: T[P], b?: T[P]) => boolean; export type CacheChanged = { - [P in keyof T]: boolean; + [P in keyof T]?: T[P]; }; export type CacheUpdateInfo = { @@ -42,7 +42,7 @@ export type CacheUpdateInfo = { * If no equal function is passed a shallow comparison is carried out between the values. * * @returns A function which can be called with wither one ar an array of properties which shall be updated. Optionally it can be called with the force param. - * This function returns a object which contains all cache properties as booleans which indicate whether the corresponding cache values really changed or not. + * This function returns a object which contains all changed cache properties, if a property isn't in this object it means that it didn't change. */ export const createCache = (cacheUpdateInfo: CacheUpdateInfo): ((propsToUpdate?: PropsToUpdate, force?: boolean) => CacheChanged) => { const cache: Cache = {} as T; @@ -64,7 +64,9 @@ export const createCache = (cacheUpdateInfo: CacheUpdateInfo): ((propsToUp const result: CacheChanged = {} as CacheChanged; each(allProps, (prop: keyof T) => { - result[prop] = !!(cache[prop]._changed || force); + if (cache[prop]._changed || force) { + result[prop] = cache[prop]._current; + } cache[prop]._changed = false; }); diff --git a/packages/overlayscrollbars/src/support/options/index.ts b/packages/overlayscrollbars/src/support/options/index.ts index a46b746..f43dcdc 100644 --- a/packages/overlayscrollbars/src/support/options/index.ts +++ b/packages/overlayscrollbars/src/support/options/index.ts @@ -20,16 +20,16 @@ export type OptionsTemplate> = { : never; }; export type OptionsValidatedResult = { - readonly foreign: PlainObject; - readonly validated: T; + readonly _foreign: PlainObject; + readonly _validated: T; }; // Options With Options Template Typings: -export type OptionsAndOptionsTemplateValue = [T, OptionsTemplateValue]; -export type OptionsAndOptionsTemplate> = { +export type OptionsWithOptionsTemplateValue = [T, OptionsTemplateValue]; +export type OptionsWithOptionsTemplate> = { [P in keyof T]: PlainObject extends T[P] - ? OptionsAndOptionsTemplate> + ? OptionsWithOptionsTemplate> : T[P] extends OptionsTemplateNativeTypes - ? OptionsAndOptionsTemplateValue + ? OptionsWithOptionsTemplateValue : never; }; type OptionsTemplateTypeMap = { diff --git a/packages/overlayscrollbars/src/support/options/transformation.ts b/packages/overlayscrollbars/src/support/options/transformation.ts index 5e6df38..bcfa8ea 100644 --- a/packages/overlayscrollbars/src/support/options/transformation.ts +++ b/packages/overlayscrollbars/src/support/options/transformation.ts @@ -1,32 +1,37 @@ -import { OptionsTemplate, OptionsAndOptionsTemplate, OptionsTemplateTypes } from 'support/options'; +import { OptionsTemplate, OptionsWithOptionsTemplate, OptionsTemplateTypes } from 'support/options'; import { PlainObject } from 'typings'; -import { isArray, isObject } from 'support/utils/types'; +import { isArray } from 'support/utils/types'; import { each, keys } from 'support/utils'; +export interface OptionsWithOptionsTemplateTransformation> { + _template: OptionsTemplate; + _options: T; +} + /** - * Transforms the given OptionsAndOptionsTemplate object to its corresponding generic (T) Object or its corresponding Template object. - * @param optionsWithOptionsTemplate The OptionsAndOptionsTemplate object which shall be converted. - * @param toTemplate True if the given OptionsAndOptionsTemplate shall be converted to its corresponding Template object. + * Transforms the given OptionsWithOptionsTemplate object to its corresponding generic (T) Object or its corresponding Template object. + * @param optionsWithOptionsTemplate The OptionsWithOptionsTemplate object which shall be converted. + * @param toTemplate True if the given OptionsWithOptionsTemplate shall be converted to its corresponding Template object. */ -export function transform>(optionsWithOptionsTemplate: OptionsAndOptionsTemplate): T; -export function transform>( - optionsWithOptionsTemplate: OptionsAndOptionsTemplate, - toTemplate: true | void -): OptionsTemplate; -export function transform>( - optionsWithOptionsTemplate: OptionsAndOptionsTemplate, - toTemplate?: true | void -): OptionsTemplate | T { - const result: any = {}; +export function transformOptions>( + optionsWithOptionsTemplate: OptionsWithOptionsTemplate +): OptionsWithOptionsTemplateTransformation { + const result: any = { + _template: {}, + _options: {}, + }; each(keys(optionsWithOptionsTemplate), (key: Extract) => { const val: PlainObject | OptionsTemplateTypes | Array = optionsWithOptionsTemplate[key]; - /* istanbul ignore else */ if (isArray(val)) { - result[key] = val[toTemplate ? 1 : 0]; - } else if (isObject(val)) { - result[key] = transform(val as OptionsAndOptionsTemplate, toTemplate); + result._template[key] = val[1]; + result._options[key] = val[0]; + } else { + // if (isObject(val)) + const tmpResult = transformOptions(val as OptionsWithOptionsTemplate); + result._template[key] = tmpResult._template; + result._options[key] = tmpResult._options; } }); diff --git a/packages/overlayscrollbars/src/support/options/validation.ts b/packages/overlayscrollbars/src/support/options/validation.ts index 21118ca..86670bd 100644 --- a/packages/overlayscrollbars/src/support/options/validation.ts +++ b/packages/overlayscrollbars/src/support/options/validation.ts @@ -61,8 +61,8 @@ const validateRecursive = ( // if the template has a object as value, it means that the options are complex (verschachtelt) if (templateIsComplex && isPlainObject(optionsValue)) { const validatedResult = validateRecursive(optionsValue, templateValue as PlainObject, optionsDiffValue, doWriteErrors, propPrefix + prop); - validatedOptions[prop] = validatedResult.validated; - optionsCopy[prop] = validatedResult.foreign as any; + validatedOptions[prop] = validatedResult._validated; + optionsCopy[prop] = validatedResult._foreign as any; each([optionsCopy, validatedOptions], (value) => { if (isEmptyObject(value[prop])) { @@ -118,8 +118,8 @@ const validateRecursive = ( }); return { - foreign: optionsCopy, - validated: validatedOptions, + _foreign: optionsCopy, + _validated: validatedOptions, }; }; @@ -140,7 +140,7 @@ const validateRecursive = ( * Without the optionsDiff object the returned validated object would be: { a: 'a', b: 'b', c: 'c' } * @param doWriteErrors True if errors shall be logged into the console, false otherwise. */ -const validate = ( +const validateOptions = ( options: T, template: OptionsTemplate>, optionsDiff?: T, @@ -158,7 +158,7 @@ const validate = ( return validateRecursive(options, template, optionsDiff || ({} as T), doWriteErrors || false); }; -export { validate, optionsTemplateTypes }; +export { validateOptions, optionsTemplateTypes }; type OptionsTemplateTypesDictionary = { readonly boolean: OptionsTemplateType; diff --git a/packages/overlayscrollbars/tests/jsdom/options.test.ts b/packages/overlayscrollbars/tests/jsdom/options.test.ts index fba6972..108dc27 100644 --- a/packages/overlayscrollbars/tests/jsdom/options.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/options.test.ts @@ -1,9 +1,9 @@ -import { validate } from 'support/options'; +import { validateOptions } from 'support/options'; import { defaultOptions, optionsTemplate } from 'options'; describe('options', () => { test('default options matching the options template', () => { - const { validated } = validate(defaultOptions, optionsTemplate); - expect(validated).toEqual(defaultOptions); + const { _validated } = validateOptions(defaultOptions, optionsTemplate); + expect(_validated).toEqual(defaultOptions); }); }); diff --git a/packages/overlayscrollbars/tests/jsdom/support/cache/cache.test.ts b/packages/overlayscrollbars/tests/jsdom/support/cache/cache.test.ts index 8428581..189f3b2 100644 --- a/packages/overlayscrollbars/tests/jsdom/support/cache/cache.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/support/cache/cache.test.ts @@ -15,45 +15,51 @@ const createUpdater = (updaterReturn: (i: number) => T) => { describe('cache', () => { describe('createCache', () => { test('creates and updates simple cache', () => { + interface Test { + number: number; + boolean: boolean; + string: string; + object: {}; + } const [updateNumberFn, updateNumber] = createUpdater((i) => i); const [updateBooleanFn, updateBoolean] = createUpdater((i) => !!(i % 2)); const [updateStringFn, updateString] = createUpdater((i) => `${i}`); const [updateObjFn, updateObj] = createUpdater((i) => ({ [i]: i })); - const updateCache = createCache({ + const updateCache = createCache({ number: updateNumber, boolean: updateBoolean, string: updateString, object: updateObj, }); - expect(updateCache('number').number).toBe(true); + expect(updateCache('number').number).toBe(1); expect(updateNumberFn).toHaveBeenCalledTimes(1); expect(updateNumberFn).toHaveBeenCalledWith(undefined, undefined); - expect(updateCache('number').number).toBe(true); + expect(updateCache('number').number).toBe(2); expect(updateNumberFn).toHaveBeenCalledTimes(2); expect(updateNumberFn).toHaveBeenCalledWith(1, undefined); - expect(updateCache('number').number).toBe(true); + expect(updateCache('number').number).toBe(3); expect(updateNumberFn).toHaveBeenCalledTimes(3); expect(updateNumberFn).toHaveBeenCalledWith(2, 1); let { string, boolean, object, number } = updateCache('number'); - expect(string).toBe(false); - expect(boolean).toBe(false); - expect(object).toBe(false); - expect(number).toBe(true); + expect(string).toBe(undefined); + expect(boolean).toBe(undefined); + expect(object).toBe(undefined); + expect(number).toBe(4); expect(updateBooleanFn).not.toHaveBeenCalled(); expect(updateStringFn).not.toHaveBeenCalled(); expect(updateObjFn).not.toHaveBeenCalled(); ({ string, boolean, object, number } = updateCache(['string', 'boolean', 'object'])); - expect(string).toBe(true); - expect(boolean).toBe(true); - expect(object).toBe(true); - expect(number).toBe(false); + expect(string).toBe('1'); + expect(boolean).toBe(!!(1 % 2)); + expect(object).toEqual({ 1: 1 }); + expect(number).toBe(undefined); expect(updateBooleanFn).toHaveBeenCalledTimes(1); expect(updateBooleanFn).toHaveBeenCalledWith(undefined, undefined); @@ -86,10 +92,10 @@ describe('cache', () => { updateCache(['string', 'boolean', 'object']); ({ string, boolean, object, number } = updateCache()); - expect(string).toBe(true); - expect(boolean).toBe(true); - expect(object).toBe(true); - expect(number).toBe(true); + expect(string).toBe('5'); + expect(boolean).toBe(!!(5 % 2)); + expect(object).toEqual({ 5: 5 }); + expect(number).toBe(5); expect(updateBooleanFn).toHaveBeenCalledTimes(5); expect(updateStringFn).toHaveBeenCalledTimes(5); @@ -103,14 +109,17 @@ describe('cache', () => { number: updateNumber, }); - expect(updateCache('number').number).toBe(true); + expect(updateCache('number').number).toBe(0); expect(updateNumberFn).toHaveBeenCalledWith(undefined, undefined); - expect(updateCache('number').number).toBe(false); + expect(updateCache('number').number).toBe(undefined); expect(updateNumberFn).toHaveBeenCalledWith(0, undefined); - expect(updateCache('number').number).toBe(false); + expect(updateCache('number').number).toBe(undefined); expect(updateNumberFn).toHaveBeenCalledWith(0, 0); + + const changed = updateCache('number'); + expect(Object.prototype.hasOwnProperty.call(changed, 'changed')).toBe(false); }); test('doesnt update if nothing changes with non primitives', () => { @@ -127,26 +136,29 @@ describe('cache', () => { ], }); - expect(updateCache('constObj').constObj).toBe(true); + expect(updateCache('constObj').constObj).toBe(constObj); expect(updateConstObjFn).toHaveBeenCalledWith(undefined, undefined); - expect(updateCache('constObj').constObj).toBe(false); + expect(updateCache('constObj').constObj).toBe(undefined); expect(updateConstObjFn).toHaveBeenCalledWith(constObj, undefined); - expect(updateCache('constObj').constObj).toBe(false); + expect(updateCache('constObj').constObj).toBe(undefined); expect(updateConstObjFn).toHaveBeenCalledWith(constObj, constObj); + expect(Object.prototype.hasOwnProperty.call(updateCache('constObj'), 'constObj')).toBe(false); - expect(updateCache('similarObj').similarObj).toBe(true); + expect(updateCache('similarObj').similarObj).toEqual(constObj); expect(updateSimilarObjFn).toHaveBeenCalledWith(undefined, undefined); - expect(updateCache('similarObj').similarObj).toBe(true); + expect(updateCache('similarObj').similarObj).toEqual(constObj); expect(updateSimilarObjFn).toHaveBeenCalledWith(constObj, undefined); - expect(updateCache('similarObj').similarObj).toBe(true); + expect(updateCache('similarObj').similarObj).toEqual(constObj); expect(updateSimilarObjFn).toHaveBeenCalledWith(constObj, constObj); + expect(Object.prototype.hasOwnProperty.call(updateCache('similarObj'), 'similarObj')).toBe(true); - expect(updateCache('comparisonObj').comparisonObj).toBe(true); + expect(updateCache('comparisonObj').comparisonObj).toEqual(constObj); expect(updateComparisonObjFn).toHaveBeenCalledWith(undefined, undefined); - expect(updateCache('comparisonObj').comparisonObj).toBe(false); + expect(updateCache('comparisonObj').comparisonObj).toBe(undefined); expect(updateComparisonObjFn).toHaveBeenCalledWith(constObj, undefined); - expect(updateCache('comparisonObj').comparisonObj).toBe(false); + expect(updateCache('comparisonObj').comparisonObj).toBe(undefined); expect(updateComparisonObjFn).toHaveBeenCalledWith(constObj, constObj); + expect(Object.prototype.hasOwnProperty.call(updateCache('comparisonObj'), 'comparisonObj')).toBe(false); }); test('updates definitely with force', () => { @@ -155,13 +167,13 @@ describe('cache', () => { number: updateNumber, }); - expect(updateCache('number', true).number).toBe(true); + expect(updateCache('number', true).number).toBe(0); expect(updateNumberFn).toHaveBeenCalledWith(undefined, undefined); - expect(updateCache('number', true).number).toBe(true); + expect(updateCache('number', true).number).toBe(0); expect(updateNumberFn).toHaveBeenCalledWith(0, undefined); - expect(updateCache('number', true).number).toBe(true); + expect(updateCache('number', true).number).toBe(0); expect(updateNumberFn).toHaveBeenCalledWith(0, 0); }); @@ -173,19 +185,21 @@ describe('cache', () => { number: [updateNumber, () => true], }); - expect(updateCache('string').string).toBe(true); + expect(updateCache('string').string).toBe('hi'); expect(updateStringFn).toHaveBeenCalledWith(undefined, undefined); - expect(updateCache('string').string).toBe(true); + expect(updateCache('string').string).toBe('hi'); expect(updateStringFn).toHaveBeenCalledWith('hi', undefined); - expect(updateCache('string').string).toBe(true); + expect(updateCache('string').string).toBe('hi'); expect(updateStringFn).toHaveBeenCalledWith('hi', 'hi'); + expect(Object.prototype.hasOwnProperty.call(updateCache('string'), 'string')).toBe(true); - expect(updateCache('number').number).toBe(false); + expect(updateCache('number').number).toBe(undefined); expect(updateNumberFn).toHaveBeenCalledWith(undefined, undefined); - expect(updateCache('number').number).toBe(false); + expect(updateCache('number').number).toBe(undefined); expect(updateNumberFn).toHaveBeenCalledWith(1, undefined); - expect(updateCache('number').number).toBe(false); + expect(updateCache('number').number).toBe(undefined); expect(updateNumberFn).toHaveBeenCalledWith(2, 1); + expect(Object.prototype.hasOwnProperty.call(updateCache('number'), 'number')).toBe(false); }); }); }); diff --git a/packages/overlayscrollbars/tests/jsdom/support/options/transformation.test.ts b/packages/overlayscrollbars/tests/jsdom/support/options/transformation.test.ts index bccda40..65d3244 100644 --- a/packages/overlayscrollbars/tests/jsdom/support/options/transformation.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/support/options/transformation.test.ts @@ -1,5 +1,5 @@ import { PlainObject } from 'typings'; -import { optionsTemplateTypes as oTypes, transform, OptionsTemplate, OptionsAndOptionsTemplate } from 'support/options'; +import { optionsTemplateTypes as oTypes, transformOptions, OptionsTemplate, OptionsWithOptionsTemplate } from 'support/options'; type TestOptionsObj = { propA: 'propA'; null: null }; type TestOptionsEnum = 'A' | 'B' | 'C'; @@ -51,7 +51,7 @@ const optionsTemplate: OptionsTemplate> = { func: oTypes.function, }; -const optionsAndOptionsTemplate: OptionsAndOptionsTemplate> = { +const TestOptionsWithOptionsTemplate: OptionsWithOptionsTemplate> = { str: [options.str, optionsTemplate.str], strArrNull: [options.strArrNull, optionsTemplate.strArrNull], nullbool: [options.nullbool, optionsTemplate.nullbool], @@ -68,10 +68,10 @@ const optionsAndOptionsTemplate: OptionsAndOptionsTemplate describe('options and options template object transformation', () => { test('transforms correctly into options object', () => { - expect(transform(optionsAndOptionsTemplate)).toEqual(options); + expect(transformOptions(TestOptionsWithOptionsTemplate)._options).toEqual(options); }); test('transforms correctly into template object', () => { - expect(transform(optionsAndOptionsTemplate, true)).toEqual(optionsTemplate); + expect(transformOptions(TestOptionsWithOptionsTemplate)._template).toEqual(optionsTemplate); }); }); diff --git a/packages/overlayscrollbars/tests/jsdom/support/options/validation.test.ts b/packages/overlayscrollbars/tests/jsdom/support/options/validation.test.ts index b2087e6..164aad4 100644 --- a/packages/overlayscrollbars/tests/jsdom/support/options/validation.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/support/options/validation.test.ts @@ -1,4 +1,4 @@ -import { validate, optionsTemplateTypes as oTypes, OptionsTemplate } from 'support/options'; +import { validateOptions, optionsTemplateTypes as oTypes, OptionsTemplate } from 'support/options'; import { assignDeep, isEmptyObject } from 'support/utils'; type TestOptionsObj = { propA: 'propA'; null: null }; @@ -56,41 +56,41 @@ describe('options validation', () => { foreignDeep: { a: 'A', b: 'B' }, }; const modifiedOptions = assignDeep({}, options, { nested: foreignObj }, foreignObj); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated).toEqual(options); + expect(_validated).toEqual(options); }); test('passed objects arent mutated', () => { const clonedOptions = assignDeep({}, options); - validate(clonedOptions, template, clonedOptions); + validateOptions(clonedOptions, template, clonedOptions); expect(clonedOptions).toEqual(options); }); test('passed object isnt returned object', () => { const clonedOptions = assignDeep({}, options); - const result = validate(clonedOptions, template); + const result = validateOptions(clonedOptions, template); - expect(result.validated).not.toBe(clonedOptions); + expect(result._validated).not.toBe(clonedOptions); }); }); describe('foreign property return', () => { test('return no foreign property', () => { - const result = validate(options, template); + const result = validateOptions(options, template); - expect(isEmptyObject(result.foreign)).toBe(true); + expect(isEmptyObject(result._foreign)).toBe(true); }); test('return signle non-object foreign property', () => { const foreignObj = { foreignProp: 'foreign' }; const modifiedOptions = assignDeep({}, options, foreignObj); - const result = validate(modifiedOptions, template); - const { foreign } = result; + const result = validateOptions(modifiedOptions, template); + const { _foreign } = result; - expect(foreign).toEqual(foreignObj); + expect(_foreign).toEqual(foreignObj); }); test('return complex foreign properties', () => { @@ -99,10 +99,10 @@ describe('options validation', () => { foreignDeep: { a: 'A', b: 'B' }, }; const modifiedOptions = assignDeep({}, options, foreignObj); - const result = validate(modifiedOptions, template); - const { foreign } = result; + const result = validateOptions(modifiedOptions, template); + const { _foreign } = result; - expect(foreign).toEqual(foreignObj); + expect(_foreign).toEqual(foreignObj); }); test('return nested complex foreign properties', () => { @@ -111,24 +111,24 @@ describe('options validation', () => { foreignDeep: { a: 'A', b: 'B' }, }; const modifiedOptions = assignDeep({}, options, { nested: foreignObj }, foreignObj); - const result = validate(modifiedOptions, template); - const { foreign } = result; + const result = validateOptions(modifiedOptions, template); + const { _foreign } = result; - expect(foreign.nested).toEqual(foreignObj); - delete foreign.nested; - expect(foreign).toEqual(foreignObj); + expect(_foreign.nested).toEqual(foreignObj); + delete _foreign.nested; + expect(_foreign).toEqual(foreignObj); }); }); describe('diff property return', () => { test('one value changed', () => { const modifiedOptions = assignDeep({}, options, { str: 'newvaluetest' }); - const result = validate(modifiedOptions, template, options); - const { validated } = result; + const result = validateOptions(modifiedOptions, template, options); + const { _validated } = result; - expect(validated.str).toBe('newvaluetest'); - delete validated.str; - expect(isEmptyObject(validated)).toBe(true); + expect(_validated.str).toBe('newvaluetest'); + delete _validated.str; + expect(isEmptyObject(_validated)).toBe(true); }); test('multiple values changed', () => { @@ -136,42 +136,42 @@ describe('options validation', () => { str: 'newvaluetest', nullbool: null, }); - const result = validate(modifiedOptions, template, options); - const { validated } = result; + const result = validateOptions(modifiedOptions, template, options); + const { _validated } = result; - expect(validated.str).toBe('newvaluetest'); - expect(validated.nullbool).toBe(null); - delete validated.str; - delete validated.nullbool; - expect(isEmptyObject(validated)).toBe(true); + expect(_validated.str).toBe('newvaluetest'); + expect(_validated.nullbool).toBe(null); + delete _validated.str; + delete _validated.nullbool; + expect(isEmptyObject(_validated)).toBe(true); }); test('one nested value changed', () => { const modifiedOptions = assignDeep({}, options, { nested: { num: -1293 } }); - const result = validate(modifiedOptions, template, options); - const { validated } = result; + const result = validateOptions(modifiedOptions, template, options); + const { _validated } = result; - expect(validated.nested?.num).toBe(-1293); - delete validated.nested?.num; - expect(isEmptyObject(validated.nested)).toBe(true); - delete validated.nested; - expect(isEmptyObject(validated)).toBe(true); + expect(_validated.nested?.num).toBe(-1293); + delete _validated.nested?.num; + expect(isEmptyObject(_validated.nested)).toBe(true); + delete _validated.nested; + expect(isEmptyObject(_validated)).toBe(true); }); test('multiple nested values changed', () => { const modifiedOptions = assignDeep({}, options, { nested: { num: -1293, abc: 'C' }, }); - const result = validate(modifiedOptions, template, options); - const { validated } = result; + const result = validateOptions(modifiedOptions, template, options); + const { _validated } = result; - expect(validated.nested?.num).toBe(-1293); - expect(validated.nested?.abc).toBe('C'); - delete validated.nested?.num; - delete validated.nested?.abc; - expect(isEmptyObject(validated.nested)).toBe(true); - delete validated.nested; - expect(isEmptyObject(validated)).toBe(true); + expect(_validated.nested?.num).toBe(-1293); + expect(_validated.nested?.abc).toBe('C'); + delete _validated.nested?.num; + delete _validated.nested?.abc; + expect(isEmptyObject(_validated.nested)).toBe(true); + delete _validated.nested; + expect(isEmptyObject(_validated)).toBe(true); }); test('various values changed', () => { @@ -182,22 +182,22 @@ describe('options validation', () => { abc: 'C', nested: { num: -1293, abc: 'C' }, }); - const result = validate(modifiedOptions, template, options); - const { validated } = result; + const result = validateOptions(modifiedOptions, template, options); + const { _validated } = result; - expect(validated.str).toBe('newstrvalue'); - expect(validated.func).toBe(newFunc); - expect(validated.abc).toBe('C'); - delete validated.str; - delete validated.func; - delete validated.abc; - expect(validated.nested?.num).toBe(-1293); - expect(validated.nested?.abc).toBe('C'); - delete validated.nested?.num; - delete validated.nested?.abc; - expect(isEmptyObject(validated.nested)).toBe(true); - delete validated.nested; - expect(isEmptyObject(validated)).toBe(true); + expect(_validated.str).toBe('newstrvalue'); + expect(_validated.func).toBe(newFunc); + expect(_validated.abc).toBe('C'); + delete _validated.str; + delete _validated.func; + delete _validated.abc; + expect(_validated.nested?.num).toBe(-1293); + expect(_validated.nested?.abc).toBe('C'); + delete _validated.nested?.num; + delete _validated.nested?.abc; + expect(isEmptyObject(_validated.nested)).toBe(true); + delete _validated.nested; + expect(isEmptyObject(_validated)).toBe(true); }); test('various values changed with foreign properties', () => { @@ -218,40 +218,40 @@ describe('options validation', () => { foreignObj, { nested: foreignObj } ); - const result = validate(modifiedOptions, template, options); - const { validated } = result; + const result = validateOptions(modifiedOptions, template, options); + const { _validated } = result; - expect(validated.str).toBe('newstrvalue'); - expect(validated.func).toBe(newFunc); - expect(validated.abc).toBe('C'); - delete validated.str; - delete validated.func; - delete validated.abc; - expect(validated.nested?.num).toBe(-1293); - expect(validated.nested?.abc).toBe('C'); - delete validated.nested?.num; - delete validated.nested?.abc; - expect(isEmptyObject(validated.nested)).toBe(true); - delete validated.nested; - expect(isEmptyObject(validated)).toBe(true); + expect(_validated.str).toBe('newstrvalue'); + expect(_validated.func).toBe(newFunc); + expect(_validated.abc).toBe('C'); + delete _validated.str; + delete _validated.func; + delete _validated.abc; + expect(_validated.nested?.num).toBe(-1293); + expect(_validated.nested?.abc).toBe('C'); + delete _validated.nested?.num; + delete _validated.nested?.abc; + expect(isEmptyObject(_validated.nested)).toBe(true); + delete _validated.nested; + expect(isEmptyObject(_validated)).toBe(true); }); }); describe('value validity', () => { test('single value doesnt match template', () => { const modifiedOptions = assignDeep({}, options, { str: 1 }); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated).not.toHaveProperty('str'); + expect(_validated).not.toHaveProperty('str'); }); test('single enum value doesnt match template', () => { const modifiedOptions = assignDeep({}, options, { abc: 'testval' }); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated).not.toHaveProperty('abc'); + expect(_validated).not.toHaveProperty('abc'); }); test('multiple values dont match template', () => { @@ -260,51 +260,51 @@ describe('options validation', () => { abc: 'testval', nullbool: 'string', }); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated).not.toHaveProperty('str'); - expect(validated).not.toHaveProperty('abc'); - expect(validated).not.toHaveProperty('nullbool'); + expect(_validated).not.toHaveProperty('str'); + expect(_validated).not.toHaveProperty('abc'); + expect(_validated).not.toHaveProperty('nullbool'); }); test('single nested value dont match template', () => { const modifiedOptions = assignDeep({}, options, { nested: { num: 'hi' } }); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated.nested).not.toHaveProperty('num'); + expect(_validated.nested).not.toHaveProperty('num'); }); test('single nested enum value dont match template', () => { const modifiedOptions = assignDeep({}, options, { nested: { abc: 'testabc' }, }); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated.nested).not.toHaveProperty('abc'); + expect(_validated.nested).not.toHaveProperty('abc'); }); test('multiple nested values dont match template', () => { const modifiedOptions = assignDeep({}, options, { nested: { num: 'hi', abc: 'testabc' }, }); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated.nested).not.toHaveProperty('num'); - expect(validated.nested).not.toHaveProperty('abc'); + expect(_validated.nested).not.toHaveProperty('num'); + expect(_validated.nested).not.toHaveProperty('abc'); }); test('all nested values dont match template', () => { const modifiedOptions = assignDeep({}, options, { nested: { num: 'hi', abc: 'testabc', switch: 1 }, }); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated).not.toHaveProperty('nested'); + expect(_validated).not.toHaveProperty('nested'); }); test('all nested values dont match template with foreign property', () => { @@ -316,10 +316,10 @@ describe('options validation', () => { switch: 1, }, }); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated).not.toHaveProperty('nested'); + expect(_validated).not.toHaveProperty('nested'); }); test('various values dont match template', () => { @@ -329,13 +329,13 @@ describe('options validation', () => { abc: 'testest', func: {}, }); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated.nested).not.toHaveProperty('switch'); - expect(validated).not.toHaveProperty('obj'); - expect(validated).not.toHaveProperty('abc'); - expect(validated).not.toHaveProperty('func'); + expect(_validated.nested).not.toHaveProperty('switch'); + expect(_validated).not.toHaveProperty('obj'); + expect(_validated).not.toHaveProperty('abc'); + expect(_validated).not.toHaveProperty('func'); }); test('various values dont match template with foreign properties', () => { @@ -355,42 +355,42 @@ describe('options validation', () => { foreignObj, { nested: foreignObj } ); - const result = validate(modifiedOptions, template); - const { validated, foreign } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated, _foreign } = result; - expect(foreign.nested).toEqual(foreignObj); - delete foreign.nested; - expect(foreign).toEqual(foreignObj); + expect(_foreign.nested).toEqual(foreignObj); + delete _foreign.nested; + expect(_foreign).toEqual(foreignObj); - expect(validated.nested).not.toHaveProperty('switch'); - expect(validated).not.toHaveProperty('obj'); - expect(validated).not.toHaveProperty('abc'); - expect(validated).not.toHaveProperty('func'); + expect(_validated.nested).not.toHaveProperty('switch'); + expect(_validated).not.toHaveProperty('obj'); + expect(_validated).not.toHaveProperty('abc'); + expect(_validated).not.toHaveProperty('func'); }); test('nested object is string', () => { const modifiedOptions = assignDeep({}, options, { nested: 'string' }); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated).not.toHaveProperty('nested'); + expect(_validated).not.toHaveProperty('nested'); }); test('nested object is null', () => { const modifiedOptions = assignDeep({}, options, { nested: null }); - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated).not.toHaveProperty('nested'); + expect(_validated).not.toHaveProperty('nested'); }); test('nested object is undefined', () => { const modifiedOptions = assignDeep({}, options); modifiedOptions.nested = undefined; - const result = validate(modifiedOptions, template); - const { validated } = result; + const result = validateOptions(modifiedOptions, template); + const { _validated } = result; - expect(validated).not.toHaveProperty('nested'); + expect(_validated).not.toHaveProperty('nested'); }); }); @@ -399,7 +399,7 @@ describe('options validation', () => { const { warn } = console; console.warn = jest.fn(); - validate(options, template, {}, true); + validateOptions(options, template, {}, true); expect(console.warn).not.toBeCalled(); console.warn = warn; @@ -410,7 +410,7 @@ describe('options validation', () => { console.warn = jest.fn(); const modifiedOptions = assignDeep({}, options, { str: 1 }); - validate(modifiedOptions, template, {}, false); + validateOptions(modifiedOptions, template, {}, false); expect(console.warn).not.toBeCalled(); console.warn = warn; @@ -421,15 +421,15 @@ describe('options validation', () => { console.warn = jest.fn(); // str must be string - validate(assignDeep({}, options, { str: 1 }), template, {}, true); + validateOptions(assignDeep({}, options, { str: 1 }), template, {}, true); expect(console.warn).toBeCalledTimes(1); // abc must be A | B | C - validate(assignDeep({}, options, { abc: 'some string' }), template, {}, true); + validateOptions(assignDeep({}, options, { abc: 'some string' }), template, {}, true); expect(console.warn).toBeCalledTimes(2); // everthing OK - validate(assignDeep({}, options, { abc: 'C' }), template, {}, true); + validateOptions(assignDeep({}, options, { abc: 'C' }), template, {}, true); expect(console.warn).toBeCalledTimes(2); console.warn = warn; diff --git a/packages/overlayscrollbars/types/support/options/index.d.ts b/packages/overlayscrollbars/types/support/options/index.d.ts index 6f0634b..c2fdc9b 100644 --- a/packages/overlayscrollbars/types/support/options/index.d.ts +++ b/packages/overlayscrollbars/types/support/options/index.d.ts @@ -13,9 +13,9 @@ export declare type OptionsValidatedResult = { readonly foreign: PlainObject; readonly validated: T; }; -export declare type OptionsAndOptionsTemplateValue = [T, OptionsTemplateValue]; -export declare type OptionsAndOptionsTemplate> = { - [P in keyof T]: PlainObject extends T[P] ? OptionsAndOptionsTemplate> : T[P] extends OptionsTemplateNativeTypes ? OptionsAndOptionsTemplateValue : never; +export declare type OptionsWithOptionsTemplateValue = [T, OptionsTemplateValue]; +export declare type OptionsWithOptionsTemplate> = { + [P in keyof T]: PlainObject extends T[P] ? OptionsWithOptionsTemplate> : T[P] extends OptionsTemplateNativeTypes ? OptionsWithOptionsTemplateValue : never; }; declare type OptionsTemplateTypeMap = { __TPL_boolean_TYPE__: boolean; diff --git a/packages/overlayscrollbars/types/support/options/transformation.d.ts b/packages/overlayscrollbars/types/support/options/transformation.d.ts index 48e3a8f..b1c5f42 100644 --- a/packages/overlayscrollbars/types/support/options/transformation.d.ts +++ b/packages/overlayscrollbars/types/support/options/transformation.d.ts @@ -1,3 +1,3 @@ -import { OptionsTemplate, OptionsAndOptionsTemplate } from 'support/options'; -export declare function transform>(optionsWithOptionsTemplate: OptionsAndOptionsTemplate): T; -export declare function transform>(optionsWithOptionsTemplate: OptionsAndOptionsTemplate, toTemplate: true | void): OptionsTemplate; +import { OptionsTemplate, OptionsWithOptionsTemplate } from 'support/options'; +export declare function transform>(optionsWithOptionsTemplate: OptionsWithOptionsTemplate): T; +export declare function transform>(optionsWithOptionsTemplate: OptionsWithOptionsTemplate, toTemplate: true | void): OptionsTemplate;