From 62e4388bc93275713813cddc215406e470030124 Mon Sep 17 00:00:00 2001 From: Rene Date: Mon, 5 Apr 2021 19:06:55 +0200 Subject: [PATCH] improve options, environment and structure setup --- .../src/environment/environment.ts | 43 +- .../src/lifecycles/lifecycleHub.ts | 14 +- .../overlayscrollbars/src/options/index.ts | 50 +- .../overlayscrollbars/src/options/options.ts | 5 +- .../overlayscrollbars/OverlayScrollbars.ts | 16 +- .../src/setups/structureSetup.ts | 51 +- .../src/support/dom/class.ts | 8 +- .../src/support/dom/manipulation.ts | 4 +- .../src/support/dom/traversal.ts | 2 +- .../src/support/options/index.ts | 21 +- .../src/support/options/validation.ts | 16 +- packages/overlayscrollbars/src/typings.ts | 8 +- .../structureLifecycle/index.browser.ts | 2 +- .../structureLifecycle/index.test.ts | 17 + .../tests/jsdom/setups/structureSetup.test.ts | 900 ++++++++++++------ .../jsdom/support/options/validation.test.ts | 36 +- .../types/lifecycles/lifecycleHub.d.ts | 10 +- .../types/options/options.d.ts | 4 +- .../overlayscrollbars/OverlayScrollbars.d.ts | 4 +- 19 files changed, 807 insertions(+), 404 deletions(-) diff --git a/packages/overlayscrollbars/src/environment/environment.ts b/packages/overlayscrollbars/src/environment/environment.ts index 0167a01..06a25c6 100644 --- a/packages/overlayscrollbars/src/environment/environment.ts +++ b/packages/overlayscrollbars/src/environment/environment.ts @@ -14,7 +14,10 @@ import { runEach, equalBCRWH, getBoundingClientRect, + assignDeep, + PartialOptions, } from 'support'; +import { OverlayScrollbarsOptions, defaultOptions } from 'options'; import { classNameEnvironment, classNameEnvironmentFlexboxGlue, @@ -22,6 +25,11 @@ import { classNameViewportScrollbarStyling, } from 'classnames'; +export interface InitializationStrategy { + _padding: boolean; + _content: boolean; +} + export type OnEnvironmentChanged = (env: Environment) => void; export interface Environment { _autoUpdateLoop: boolean; @@ -33,11 +41,23 @@ export interface Environment { _cssCustomProperties: boolean; _addListener(listener: OnEnvironmentChanged): void; _removeListener(listener: OnEnvironmentChanged): void; + _getInitializationStrategy(): InitializationStrategy; + _setInitializationStrategy(newInitializationStrategy: Partial): void; + _getDefaultOptions(): OverlayScrollbarsOptions; + _setDefaultOptions(newDefaultOptions: PartialOptions): void; + _defaultInitializationStrategy: InitializationStrategy; + _defaultDefaultOptions: OverlayScrollbarsOptions; } let environmentInstance: Environment; const { abs, round } = Math; +const diffBiggerThanOne = (valOne: number, valTwo: number): boolean => { + const absValOne = abs(valOne); + const absValTwo = abs(valTwo); + return !(absValOne === absValTwo || absValOne + 1 === absValTwo || absValOne - 1 === absValTwo); +}; + const getNativeScrollbarSize = (body: HTMLElement, measureElm: HTMLElement): XY => { appendChildren(body, measureElm); const cSize = clientSize(measureElm); @@ -111,18 +131,16 @@ const getWindowDPR = (): number => { return window.devicePixelRatio || dDPI / sDPI; }; -const diffBiggerThanOne = (valOne: number, valTwo: number): boolean => { - const absValOne = abs(valOne); - const absValTwo = abs(valTwo); - return !(absValOne === absValTwo || absValOne + 1 === absValTwo || absValOne - 1 === absValTwo); -}; +const getDefaultInitializationStrategy = (nativeScrollbarStyling: boolean): InitializationStrategy => ({ + _padding: !nativeScrollbarStyling, + _content: false, +}); const createEnvironment = (): Environment => { const { body } = document; const envDOM = createDOM(`
`); const envElm = envDOM[0] as HTMLElement; const envChildElm = envElm.firstChild as HTMLElement; - const onChangedListener: Set = new Set(); const nativeScrollbarSize = getNativeScrollbarSize(body, envElm); const nativeScrollbarStyling = false; //getNativeScrollbarStyling(envElm); //TODO: Re - enable; @@ -130,6 +148,9 @@ const createEnvironment = (): Environment => { x: nativeScrollbarSize.x === 0, y: nativeScrollbarSize.y === 0, }; + const defaultInitializationStrategy = getDefaultInitializationStrategy(nativeScrollbarStyling); + let initializationStrategy = defaultInitializationStrategy; + let defaultDefaultOptions = defaultOptions; const env: Environment = { _autoUpdateLoop: false, @@ -145,6 +166,16 @@ const createEnvironment = (): Environment => { _removeListener(listener: OnEnvironmentChanged): void { onChangedListener.delete(listener); }, + _getInitializationStrategy: () => ({ ...initializationStrategy }), + _setInitializationStrategy(newInitializationStrategy) { + initializationStrategy = assignDeep({}, initializationStrategy, newInitializationStrategy); + }, + _getDefaultOptions: () => ({ ...defaultDefaultOptions }), + _setDefaultOptions(newDefaultOptions) { + defaultDefaultOptions = assignDeep({}, defaultDefaultOptions, newDefaultOptions); + }, + _defaultInitializationStrategy: defaultInitializationStrategy, + _defaultDefaultOptions: defaultDefaultOptions, }; removeAttr(envElm, 'style'); diff --git a/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts b/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts index 5c46daa..ba0bb19 100644 --- a/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts +++ b/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts @@ -1,5 +1,5 @@ -import { XY, TRBL, CacheValues, each, push, keys, OptionsValidated, hasOwnProperty, isNumber, scrollLeft, scrollTop } from 'support'; -import { Options } from 'options'; +import { XY, TRBL, CacheValues, PartialOptions, each, push, keys, hasOwnProperty, isNumber, scrollLeft, scrollTop } from 'support'; +import { OverlayScrollbarsOptions } from 'options'; import { getEnvironment } from 'environment'; import { StructureSetup } from 'setups/structureSetup'; import { createTrinsicLifecycle } from 'lifecycles/trinsicLifecycle'; @@ -41,12 +41,12 @@ export type Lifecycle = ( ) => Partial | void; export interface LifecycleHubInstance { - _update(changedOptions?: OptionsValidated | null, force?: boolean): void; + _update(changedOptions?: PartialOptions | null, force?: boolean): void; _destroy(): void; } export interface LifecycleHub { - _options: Options; + _options: OverlayScrollbarsOptions; _structureSetup: StructureSetup; // whether the "viewport arrange" strategy must be used (true if no native scrollbar hiding and scrollbars are overlaid) _doViewportArrange: boolean; @@ -108,7 +108,7 @@ const heightIntrinsicCacheValuesFallback: CacheValues = { _changed: false, }; -export const createLifecycleHub = (options: Options, structureSetup: StructureSetup): LifecycleHubInstance => { +export const createLifecycleHub = (options: OverlayScrollbarsOptions, structureSetup: StructureSetup): LifecycleHubInstance => { let paddingInfo = paddingInfoFallback; let viewportPaddingStyle = viewportPaddingStyleFallback; let viewportOverflowScroll = viewportOverflowScrollFallback; @@ -146,7 +146,7 @@ export const createLifecycleHub = (options: Options, structureSetup: StructureSe const updateLifecycles = ( updateHints?: Partial | null, - changedOptions?: OptionsValidated | null, + changedOptions?: Partial | null, force?: boolean ) => { let { @@ -260,7 +260,7 @@ export const createLifecycleHub = (options: Options, structureSetup: StructureSe */ }); - const update = (changedOptions?: OptionsValidated | null, force?: boolean) => { + const update = (changedOptions?: Partial | null, force?: boolean) => { updateLifecycles(null, changedOptions, force); }; const envUpdateListener = update.bind(null, null, true); diff --git a/packages/overlayscrollbars/src/options/index.ts b/packages/overlayscrollbars/src/options/index.ts index 7a2bfaf..5fefb53 100644 --- a/packages/overlayscrollbars/src/options/index.ts +++ b/packages/overlayscrollbars/src/options/index.ts @@ -24,35 +24,35 @@ export type SizeChangedCallback = (this: any, args?: SizeChangedArgs) => void; export type UpdatedCallback = (this: any, args?: UpdatedArgs) => void; -export interface Options { - resize?: ResizeBehavior; - paddingAbsolute?: boolean; - updating?: { - elementEvents?: ReadonlyArray<[string, string]> | null; - contentMutationDebounce?: number; - hostMutationDebounce?: number; - resizeDebounce?: number; +export interface OverlayScrollbarsOptions { + resize: ResizeBehavior; + paddingAbsolute: boolean; + updating: { + elementEvents: ReadonlyArray<[string, string]> | null; + contentMutationDebounce: number; + hostMutationDebounce: number; + resizeDebounce: number; }; - overflow?: { - x?: OverflowBehavior; - y?: OverflowBehavior; + overflow: { + x: OverflowBehavior; + y: OverflowBehavior; }; - scrollbars?: { - visibility?: VisibilityBehavior; - autoHide?: AutoHideBehavior; - autoHideDelay?: number; - dragScroll?: boolean; - clickScroll?: boolean; - touch?: boolean; + scrollbars: { + visibility: VisibilityBehavior; + autoHide: AutoHideBehavior; + autoHideDelay: number; + dragScroll: boolean; + clickScroll: boolean; + touch: boolean; }; - textarea?: { - dynWidth?: boolean; - dynHeight?: boolean; - inheritedAttrs?: string | ReadonlyArray | null; + textarea: { + dynWidth: boolean; + dynHeight: boolean; + inheritedAttrs: string | ReadonlyArray | null; }; - nativeScrollbarsOverlaid?: { - show?: boolean; - initialize?: boolean; + nativeScrollbarsOverlaid: { + show: boolean; + initialize: boolean; }; /* callbacks?: { diff --git a/packages/overlayscrollbars/src/options/options.ts b/packages/overlayscrollbars/src/options/options.ts index 5577216..3cb3ecb 100644 --- a/packages/overlayscrollbars/src/options/options.ts +++ b/packages/overlayscrollbars/src/options/options.ts @@ -6,7 +6,7 @@ import { OptionsWithOptionsTemplate, Func, } from 'support/options'; -import { ResizeBehavior, OverflowBehavior, VisibilityBehavior, AutoHideBehavior, Options } from 'options'; +import { ResizeBehavior, OverflowBehavior, VisibilityBehavior, AutoHideBehavior, OverlayScrollbarsOptions } from 'options'; const numberAllowedValues: OptionsTemplateValue = oTypes.number; const stringArrayNullAllowedValues: OptionsTemplateValue | null> = [oTypes.string, oTypes.array, oTypes.null]; @@ -33,7 +33,8 @@ 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: OptionsWithOptionsTemplate> = { + +const defaultOptionsWithTemplate: OptionsWithOptionsTemplate = { resize: ['none', resizeAllowedValues], // none || both || horizontal || vertical || n || b || h || v paddingAbsolute: booleanFalseTemplate, // true || false updating: { diff --git a/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts b/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts index 8047aab..f7f6dab 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts @@ -1,20 +1,22 @@ import { OSTarget, OSTargetObject } from 'typings'; -import { validateOptions, assignDeep, isEmptyObject } from 'support'; +import { PartialOptions, validateOptions, assignDeep, isEmptyObject } from 'support'; import { createStructureSetup, StructureSetup } from 'setups/structureSetup'; import { createLifecycleHub } from 'lifecycles/lifecycleHub'; -import { Options, defaultOptions, optionsTemplate } from 'options'; +import { OverlayScrollbarsOptions, optionsTemplate } from 'options'; +import { getEnvironment } from 'environment'; -const OverlayScrollbars = (target: OSTarget | OSTargetObject, options?: Options, extensions?: any): any => { - const currentOptions: Required = assignDeep( +const OverlayScrollbars = (target: OSTarget | OSTargetObject, options?: PartialOptions, extensions?: any): any => { + const { _getDefaultOptions } = getEnvironment(); + const currentOptions: OverlayScrollbarsOptions = assignDeep( {}, - defaultOptions, - validateOptions(options || ({} as Options), optionsTemplate, null, true)._validated + _getDefaultOptions(), + validateOptions(options || ({} as PartialOptions), optionsTemplate, null, true)._validated ); const structureSetup: StructureSetup = createStructureSetup(target); const lifecycleHub = createLifecycleHub(currentOptions, structureSetup); const instance = { - options(newOptions?: Options) { + options(newOptions?: PartialOptions) { if (newOptions) { const { _validated: _changedOptions } = validateOptions(newOptions, optionsTemplate, currentOptions, true); diff --git a/packages/overlayscrollbars/src/setups/structureSetup.ts b/packages/overlayscrollbars/src/setups/structureSetup.ts index 313c3cb..78bde43 100644 --- a/packages/overlayscrollbars/src/setups/structureSetup.ts +++ b/packages/overlayscrollbars/src/setups/structureSetup.ts @@ -14,6 +14,7 @@ import { runEach, insertBefore, attr, + isBoolean, } from 'support'; import { classNameHost, @@ -24,7 +25,7 @@ import { classNameViewportScrollbarStyling, } from 'classnames'; import { getEnvironment } from 'environment'; -import { OSTarget, OSTargetObject, InternalVersionOf, OSTargetElement } from 'typings'; +import { OSTarget, OSTargetObject, OSTargetElement } from 'typings'; export interface OSTargetContext { _isTextarea: boolean; @@ -35,9 +36,13 @@ export interface OSTargetContext { _documentElm: HTMLDocument; } -export interface PreparedOSTargetObject extends Required> { +export interface PreparedOSTargetObject { + _target: OSTargetElement; _host: HTMLElement; - _viewportArrange: HTMLStyleElement | null; + _viewport: HTMLElement; + _padding: HTMLElement | false | null; + _content: HTMLElement | false | null; + _viewportArrange: HTMLStyleElement | false | null; } export interface StructureSetup { @@ -46,13 +51,13 @@ export interface StructureSetup { _destroy: () => void; } -const unwrap = (elm: HTMLElement | null | undefined) => { +const unwrap = (elm: HTMLElement | false | null | undefined) => { appendChildren(parent(elm), contents(elm)); removeElements(elm); }; let contentArrangeCounter = 0; -const createUniqueViewportArrangeElement = () => { +const createUniqueViewportArrangeElement = (): HTMLStyleElement => { const elm = document.createElement('style'); attr(elm, 'id', `${classNameViewportArrange}-${contentArrangeCounter}`); @@ -60,26 +65,32 @@ const createUniqueViewportArrangeElement = () => { return elm; }; +const evaluateCreationFromStrategy = (initializationValue: HTMLElement | boolean | undefined, strategy: boolean): HTMLElement | false | undefined => { + const isBooleanValue = isBoolean(initializationValue); + if (isBooleanValue || isUndefined(initializationValue)) { + return (isBooleanValue ? initializationValue : strategy) && undefined; + } + return initializationValue as HTMLElement; +}; export const createStructureSetup = (target: OSTarget | OSTargetObject): StructureSetup => { + const { _getInitializationStrategy, _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _cssCustomProperties } = getEnvironment(); + const { _padding: paddingNeeded, _content: contentNeeded } = _getInitializationStrategy(); const targetIsElm = isHTMLElement(target); - const osTargetObj: InternalVersionOf = targetIsElm - ? ({} as InternalVersionOf) + const osTargetObj: Partial = targetIsElm + ? ({} as Partial) : { _host: (target as OSTargetObject).host, _target: (target as OSTargetObject).target, - _padding: (target as OSTargetObject).padding, _viewport: (target as OSTargetObject).viewport, - _content: (target as OSTargetObject).content, + _padding: evaluateCreationFromStrategy((target as OSTargetObject).padding, paddingNeeded), + _content: evaluateCreationFromStrategy((target as OSTargetObject).content, contentNeeded), }; if (targetIsElm) { - const padding = createDiv(classNamePadding); const viewport = createDiv(classNameViewport); - const content = createDiv(classNameContent); - - appendChildren(padding, viewport); - appendChildren(viewport, content); + const padding = paddingNeeded && createDiv(classNamePadding); + const content = contentNeeded && createDiv(classNameContent); osTargetObj._target = target as OSTargetElement; osTargetObj._padding = padding; @@ -106,12 +117,15 @@ export const createStructureSetup = (target: OSTarget | OSTargetObject): Structu } if (targetIsElm) { - appendChildren(_content!, getTargetContents(_target)); + const contentSlot = _content || _viewport; + appendChildren(contentSlot, getTargetContents(_target!)); appendChildren(_host, _padding); + appendChildren(_padding || _host, _viewport); + appendChildren(_viewport, _content); push(destroyFns, () => { - appendChildren(_host, contents(_content)); - removeElements(_padding); + appendChildren(_host, contents(contentSlot)); + removeElements(_padding || _viewport); removeClass(_host, classNameHost); }); } else { @@ -154,7 +168,7 @@ export const createStructureSetup = (target: OSTarget | OSTargetObject): Structu addClass(_viewport, classNameViewport); addClass(_content, classNameContent); - const ownerDocument: HTMLDocument = _target.ownerDocument; + const ownerDocument: HTMLDocument = _target!.ownerDocument; const bodyElm = ownerDocument.body as HTMLBodyElement; const wnd = ownerDocument.defaultView as Window; const ctx: OSTargetContext = { @@ -171,7 +185,6 @@ export const createStructureSetup = (target: OSTarget | OSTargetObject): Structu _host, }; - const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _cssCustomProperties } = getEnvironment(); if (_nativeScrollbarStyling) { push(destroyFns, removeClass.bind(0, _viewport, classNameViewportScrollbarStyling)); } else if (!_cssCustomProperties && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y)) { diff --git a/packages/overlayscrollbars/src/support/dom/class.ts b/packages/overlayscrollbars/src/support/dom/class.ts index 7708a34..efc0930 100644 --- a/packages/overlayscrollbars/src/support/dom/class.ts +++ b/packages/overlayscrollbars/src/support/dom/class.ts @@ -4,7 +4,7 @@ import { keys } from 'support/utils/object'; const rnothtmlwhite = /[^\x20\t\r\n\f]+/g; const classListAction = ( - elm: Element | null | undefined, + elm: Element | false | null | undefined, className: string, action: (elmClassList: DOMTokenList, clazz: string) => boolean | void ): boolean => { @@ -27,7 +27,7 @@ const classListAction = ( * @param elm The element. * @param className The class name(s). */ -export const hasClass = (elm: Element | null | undefined, className: string): boolean => +export const hasClass = (elm: Element | false | null | undefined, className: string): boolean => classListAction(elm, className, (classList, clazz) => classList.contains(clazz)); /** @@ -35,7 +35,7 @@ export const hasClass = (elm: Element | null | undefined, className: string): bo * @param elm The element. * @param className The class name(s) which shall be added. (separated by spaces) */ -export const addClass = (elm: Element | null | undefined, className: string): void => { +export const addClass = (elm: Element | false | null | undefined, className: string): void => { classListAction(elm, className, (classList, clazz) => classList.add(clazz)); }; @@ -44,7 +44,7 @@ export const addClass = (elm: Element | null | undefined, className: string): vo * @param elm The element. * @param className The class name(s) which shall be removed. (separated by spaces) */ -export const removeClass = (elm: Element | null | undefined, className: string): void => { +export const removeClass = (elm: Element | false | null | undefined, className: string): void => { classListAction(elm, className, (classList, clazz) => classList.remove(clazz)); }; diff --git a/packages/overlayscrollbars/src/support/dom/manipulation.ts b/packages/overlayscrollbars/src/support/dom/manipulation.ts index 14b4ba6..49c2094 100644 --- a/packages/overlayscrollbars/src/support/dom/manipulation.ts +++ b/packages/overlayscrollbars/src/support/dom/manipulation.ts @@ -2,7 +2,7 @@ import { isArrayLike } from 'support/utils/types'; import { each, from } from 'support/utils/array'; import { parent } from 'support/dom/traversal'; -type NodeCollection = ArrayLike | Node | null | undefined; +type NodeCollection = ArrayLike | Node | false | null | undefined; /** * Inserts Nodes before the given preferredAnchor element. @@ -10,7 +10,7 @@ type NodeCollection = ArrayLike | Node | null | undefined; * @param preferredAnchor The element before which the Nodes shall be inserted or null if the elements shall be appended at the end. * @param insertedElms The Nodes which shall be inserted. */ -const before = (parentElm: Node | null | undefined, preferredAnchor: Node | null | undefined, insertedElms: NodeCollection): void => { +const before = (parentElm: Node | false | null | undefined, preferredAnchor: Node | null | undefined, insertedElms: NodeCollection): void => { if (insertedElms) { let anchor: Node | null | undefined = preferredAnchor; let fragment: DocumentFragment | Node | null | undefined; diff --git a/packages/overlayscrollbars/src/support/dom/traversal.ts b/packages/overlayscrollbars/src/support/dom/traversal.ts index 364c90e..b89be2a 100644 --- a/packages/overlayscrollbars/src/support/dom/traversal.ts +++ b/packages/overlayscrollbars/src/support/dom/traversal.ts @@ -1,7 +1,7 @@ import { isElement } from 'support/utils/types'; import { push, from } from 'support/utils/array'; -type InputElementType = Element | Node | null | undefined; +type InputElementType = Element | Node | false | null | undefined; type OutputElementType = Element | null; const elmPrototype = Element.prototype; diff --git a/packages/overlayscrollbars/src/support/options/index.ts b/packages/overlayscrollbars/src/support/options/index.ts index cb0964a..79983ee 100644 --- a/packages/overlayscrollbars/src/support/options/index.ts +++ b/packages/overlayscrollbars/src/support/options/index.ts @@ -3,6 +3,8 @@ import { PlainObject } from 'typings'; export * from 'support/options/validation'; export * from 'support/options/transformation'; +type ObjectType = Record; + export type Func = (this: any, ...args: any[]) => any; export type OptionsTemplateType = ExtractPropsKey; export type OptionsTemplateTypes = keyof OptionsTemplateTypeMap; @@ -12,29 +14,26 @@ export type OptionsTemplateValue ? OptionsTemplateValueNonEnum : string : OptionsTemplateValueNonEnum; -export type OptionsTemplate> = { - [P in keyof T]: PlainObject extends T[P] - ? OptionsTemplate> - : T[P] extends OptionsTemplateNativeTypes - ? OptionsTemplateValue - : never; -}; -export type OptionsValidated = { - [P in keyof T]?: T[P]; +export type OptionsTemplate = { + [P in keyof T]: T[P] extends ObjectType ? OptionsTemplate : T[P] extends OptionsTemplateNativeTypes ? OptionsTemplateValue : never; }; export type OptionsValidationResult = { readonly _foreign: PlainObject; - readonly _validated: OptionsValidated; + readonly _validated: PartialOptions; }; // Options With Options Template Typings: export type OptionsWithOptionsTemplateValue = [T, OptionsTemplateValue]; export type OptionsWithOptionsTemplate> = { - [P in keyof T]: PlainObject extends T[P] + [P in keyof T]: T[P] extends ObjectType ? OptionsWithOptionsTemplate> : T[P] extends OptionsTemplateNativeTypes ? OptionsWithOptionsTemplateValue : never; }; +export type PartialOptions = { + [P in keyof T]?: T[P] extends ObjectType ? PartialOptions : T[P]; +}; + type OptionsTemplateTypeMap = { __TPL_boolean_TYPE__: boolean; __TPL_number_TYPE__: number; diff --git a/packages/overlayscrollbars/src/support/options/validation.ts b/packages/overlayscrollbars/src/support/options/validation.ts index 0837d79..a75382b 100644 --- a/packages/overlayscrollbars/src/support/options/validation.ts +++ b/packages/overlayscrollbars/src/support/options/validation.ts @@ -1,6 +1,6 @@ import { each, hasOwnProperty, keys, push, isEmptyObject } from 'support/utils'; import { type, isArray, isUndefined, isPlainObject, isString } from 'support/utils/types'; -import { OptionsTemplate, OptionsTemplateTypes, OptionsTemplateType, Func, OptionsValidationResult, OptionsValidated } from 'support/options'; +import { OptionsTemplate, OptionsTemplateTypes, OptionsTemplateType, PartialOptions, Func, OptionsValidationResult } from 'support/options'; import { PlainObject } from 'typings'; const { stringify } = JSON; @@ -41,14 +41,14 @@ const optionsTemplateTypes: OptionsTemplateTypesDictionary = ['boolean', 'number * @param propPath The propertyPath which lead to this object. (used for error logging) */ const validateRecursive = ( - options: T, - template: OptionsTemplate>, + options: PartialOptions, + template: OptionsTemplate, optionsDiff: T, doWriteErrors?: boolean, propPath?: string ): OptionsValidationResult => { - const validatedOptions: OptionsValidated = {}; - const optionsCopy: T = { ...options }; + const validatedOptions: PartialOptions = {}; + const optionsCopy: PartialOptions = { ...options }; const props = keys(template).filter((prop) => hasOwnProperty(options, prop)); each(props, (prop: Extract) => { @@ -60,7 +60,7 @@ 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); + const validatedResult = validateRecursive(optionsValue, templateValue as T, optionsDiffValue, doWriteErrors, propPrefix + prop); validatedOptions[prop] = validatedResult._validated as any; optionsCopy[prop] = validatedResult._foreign as any; @@ -147,8 +147,8 @@ const validateRecursive = ( * @param doWriteErrors True if errors shall be logged into the console, false otherwise. */ const validateOptions = ( - options: T, - template: OptionsTemplate>, + options: PartialOptions, + template: OptionsTemplate, optionsDiff?: T | null, doWriteErrors?: boolean ): OptionsValidationResult => { diff --git a/packages/overlayscrollbars/src/typings.ts b/packages/overlayscrollbars/src/typings.ts index c18f5bc..0a8e280 100644 --- a/packages/overlayscrollbars/src/typings.ts +++ b/packages/overlayscrollbars/src/typings.ts @@ -18,14 +18,16 @@ export type OSTargetElement = HTMLElement | HTMLTextAreaElement; * If element is provided, the provided element takes all its responsibilities. * DOM hierarchy isn't checked in this case, its assumed that hieararchy is correct in such a case. * - * If element is null it won't be generated, and the responsibilities (feautes) of this element are lost. + * Undefined means that the plugin decides whether the respective element needs to be added or can be savely omitted. + * True means that even if the plugin would decide to not generate the element, the element is still generated. + * False means that event if the plugin would decide to generate the element, the element won't be generated. */ export interface OSTargetObject { target: OSTargetElement; host?: HTMLElement; - padding?: HTMLElement | null; + padding?: HTMLElement | boolean; viewport?: HTMLElement; - content?: HTMLElement | null; + content?: HTMLElement | boolean; } export type OSTarget = OSTargetElement | OSTargetObject; diff --git a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts index 461fd3d..ca7f436 100644 --- a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts +++ b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.browser.ts @@ -4,7 +4,7 @@ import { createDiv, appendChildren, parent, style, on, off, addClass, WH, XY, cl import { OverlayScrollbars } from 'overlayscrollbars/OverlayScrollbars'; const targetElm = document.querySelector('#target') as HTMLElement; -window.os = OverlayScrollbars({ target: targetElm }); +window.os = OverlayScrollbars({ target: targetElm, content: null }); export const resize = (element: HTMLElement) => { const strMouseTouchDownEvent = 'mousedown touchstart'; diff --git a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.test.ts b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.test.ts index fb23f45..7398374 100644 --- a/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.test.ts +++ b/packages/overlayscrollbars/tests/browser/lifecycles/structureLifecycle/index.test.ts @@ -3,6 +3,23 @@ import 'expect-playwright'; import { Environment } from 'environment'; import url from './.build/build.html'; +/** + * env test cases: + * 1. overlaid scrollbars + * - with scrollbar styling + * - without scrollbar styling + * - with css custom properties + * - without css custom properties + * 2. partially overlaid, partially normal scrollbars + * - with scrollbar styling + * - without scrollbar styling + * - with css custom properties + * - without css custom properties + * 3. normal scrollbars + * - with scrollbar styling + * - without scrollbar styling + */ + describe('StructureLifecycle', () => { beforeAll(async () => { await page.goto(url); diff --git a/packages/overlayscrollbars/tests/jsdom/setups/structureSetup.test.ts b/packages/overlayscrollbars/tests/jsdom/setups/structureSetup.test.ts index e067831..77d83e2 100644 --- a/packages/overlayscrollbars/tests/jsdom/setups/structureSetup.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/setups/structureSetup.test.ts @@ -1,4 +1,19 @@ +import { Environment } from 'environment'; +import { OSTarget, OSTargetObject } from 'typings'; import { createStructureSetup, StructureSetup } from 'setups/structureSetup'; +import { isHTMLElement } from 'support'; + +const mockGetEnvironment = jest.fn(); +jest.mock('environment', () => { + return { + getEnvironment: jest.fn().mockImplementation(() => mockGetEnvironment()), + }; +}); + +interface StructureSetupProxy { + input: OSTarget | OSTargetObject; + setup: StructureSetup; +} const textareaId = 'textarea'; const textareaHostId = 'host'; @@ -69,7 +84,13 @@ const assertCorrectDOMStructure = (textarea?: boolean) => { } }; -const assertCorrectSetup = (textarea: boolean, setup: StructureSetup) => { +const createStructureSetupProxy = (target: OSTarget | OSTargetObject): StructureSetupProxy => ({ + input: target, + setup: createStructureSetup(target), +}); + +const assertCorrectSetup = (textarea: boolean, setupProxy: StructureSetupProxy, environment: Environment): StructureSetup => { + const { input, setup } = setupProxy; const { _targetObj, _targetCtx, _destroy } = setup; const { _target, _host, _padding, _viewport, _content } = _targetObj; const { target, host, padding, viewport, content } = getElements(textarea); @@ -113,6 +134,43 @@ const assertCorrectSetup = (textarea: boolean, setup: StructureSetup) => { expect(typeof _destroy).toBe('function'); + const { _nativeScrollbarStyling, _cssCustomProperties, _getInitializationStrategy } = environment; + const { _padding: paddingNeeded, _content: contentNeeded } = _getInitializationStrategy(); + const inputIsElement = isHTMLElement(input); + const inputAsObj = input as OSTargetObject; + const styleElm = document.querySelector('style'); + const checkStrategyDependendElements = (elm: Element | null, input: HTMLElement | boolean | undefined, strategy: boolean) => { + if (input) { + expect(elm).toBeTruthy(); + } else { + if (input === false) { + expect(elm).toBeFalsy(); + } + if (input === undefined) { + if (strategy) { + expect(elm).toBeTruthy(); + } else { + expect(elm).toBeFalsy(); + } + } + } + }; + + if (_nativeScrollbarStyling || _cssCustomProperties) { + expect(styleElm).toBeFalsy(); + } else { + expect(styleElm).toBeTruthy(); + } + + if (inputIsElement) { + checkStrategyDependendElements(padding, undefined, paddingNeeded); + checkStrategyDependendElements(content, undefined, contentNeeded); + } else { + const { padding: inputPadding, content: inputContent } = inputAsObj; + checkStrategyDependendElements(padding, inputPadding, paddingNeeded); + checkStrategyDependendElements(content, inputContent, contentNeeded); + } + return setup; }; @@ -133,314 +191,594 @@ const assertCorrectDestroy = (snapshot: string, setup: StructureSetup) => { expect(snapshot).toBe(getSnapshot()); }; +const env: Environment = jest.requireActual('environment').getEnvironment(); +const envDefault = { + name: 'default', + env: env, +}; +const envNativeScrollbarStyling = { + name: 'native scrollbar styling', + env: { + ...env, + _nativeScrollbarStyling: true, + }, +}; +const envCssCustomProperties = { + name: 'custom css properties', + env: { + ...env, + _cssCustomProperties: true, + }, +}; +const envInitStrategyMin = { + name: 'initialization strategy min', + env: { + ...env, + _getInitializationStrategy: () => ({ + _content: false, + _padding: false, + }), + }, +}; +const envInitStrategyMax = { + name: 'initialization strategy max', + env: { + ...env, + _getInitializationStrategy: () => ({ + _content: true, + _padding: true, + }), + }, +}; + describe('structureSetup', () => { afterEach(() => clearBody()); - [false, true].forEach((isTextarea) => { - describe(isTextarea ? 'textarea' : 'element', () => { - describe('basic', () => { - test('Element', () => { - const snapshot = fillBody(isTextarea); - const setup = assertCorrectSetup(isTextarea, createStructureSetup(getTarget(isTextarea))); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); - - test('Object', () => { - const snapshot = fillBody(isTextarea); - const setup = assertCorrectSetup(isTextarea, createStructureSetup({ target: getTarget(isTextarea) })); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); + [envDefault, envNativeScrollbarStyling, envCssCustomProperties, envInitStrategyMin, envInitStrategyMax].forEach((envWithName) => { + const { env: currEnv, name } = envWithName; + describe(`Environment: ${name}`, () => { + beforeAll(() => { + mockGetEnvironment.mockImplementation(() => currEnv); }); - describe('complex', () => { - describe('single assigned', () => { - test('padding', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; + [false, true].forEach((isTextarea) => { + describe(isTextarea ? 'textarea' : 'element', () => { + describe('basic', () => { + test('Element', () => { + const snapshot = fillBody(isTextarea); + const setup = assertCorrectSetup(isTextarea, createStructureSetupProxy(getTarget(isTextarea)), currEnv); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - padding: document.querySelector('#padding')!, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); - test('viewport', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; + test('Object', () => { + const snapshot = fillBody(isTextarea); + const setup = assertCorrectSetup(isTextarea, createStructureSetupProxy({ target: getTarget(isTextarea) }), currEnv); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - viewport: document.querySelector('#viewport')!, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); }); - test('content', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; + describe('complex', () => { + describe('single assigned', () => { + test('padding', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: document.querySelector('#padding')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + viewport: document.querySelector('#viewport')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('content', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + content: document.querySelector('#content')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - content: document.querySelector('#content')!, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); - }); - describe('multiple assigned', () => { - test('padding viewport content', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; + describe('multiple assigned', () => { + test('padding viewport content', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: document.querySelector('#padding')!, + viewport: document.querySelector('#viewport')!, + content: document.querySelector('#content')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('padding viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: document.querySelector('#padding')!, + viewport: document.querySelector('#viewport')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('padding content', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: document.querySelector('#padding')!, + content: document.querySelector('#content')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('viewport content', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + viewport: document.querySelector('#viewport')!, + content: document.querySelector('#content')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - padding: document.querySelector('#padding')!, - viewport: document.querySelector('#viewport')!, - content: document.querySelector('#content')!, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); - test('padding viewport', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; + describe('single false', () => { + test('padding', () => { + const snapshot = fillBody(isTextarea); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + target: getTarget(isTextarea), + padding: false, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('content', () => { + const snapshot = fillBody(isTextarea); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + target: getTarget(isTextarea), + content: false, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - padding: document.querySelector('#padding')!, - viewport: document.querySelector('#viewport')!, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); - test('padding content', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; + describe('single true', () => { + test('padding', () => { + const snapshot = fillBody(isTextarea); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + target: getTarget(isTextarea), + padding: true, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('content', () => { + const snapshot = fillBody(isTextarea); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + target: getTarget(isTextarea), + content: true, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - padding: document.querySelector('#padding')!, - content: document.querySelector('#content')!, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); - test('viewport content', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; + describe('multiple false', () => { + test('padding & content', () => { + const snapshot = fillBody(isTextarea); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + target: getTarget(isTextarea), + padding: false, + content: false, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - viewport: document.querySelector('#viewport')!, - content: document.querySelector('#content')!, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); - }); - describe('single null', () => { - test('padding', () => { - const snapshot = fillBody(isTextarea); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - target: getTarget(isTextarea), - padding: null, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); - - test('content', () => { - const snapshot = fillBody(isTextarea); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - target: getTarget(isTextarea), - content: null, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); - }); - - describe('multiple null', () => { - test('padding & content', () => { - const snapshot = fillBody(isTextarea); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - target: getTarget(isTextarea), - padding: null, - content: null, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); - }); - - describe('mixed', () => { - test('null: padding & content | assigned: viewport', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; + describe('multiple true', () => { + test('padding & content', () => { + const snapshot = fillBody(isTextarea); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + target: getTarget(isTextarea), + padding: true, + content: true, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - padding: null, - viewport: document.querySelector('#viewport')!, - content: null, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); - test('null: padding | assigned: content', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; - }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - padding: null, - content: document.querySelector('#content')!, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); + describe('mixed', () => { + test('false: padding & content | assigned: viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: false, + viewport: document.querySelector('#viewport')!, + content: false, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); - test('null: padding | assigned: viewport', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; - }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - padding: null, - viewport: document.querySelector('#viewport')!, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); + test('true: padding & content | assigned: viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: true, + viewport: document.querySelector('#viewport')!, + content: true, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); - test('null: padding | assigned: viewport & content', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; - }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - viewport: document.querySelector('#viewport')!, - padding: null, - content: document.querySelector('#content')!, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); + test('true: content | false: padding | assigned: viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: false, + viewport: document.querySelector('#viewport')!, + content: true, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); - test('null: content | assigned: padding', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; - }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - padding: document.querySelector('#padding')!, - content: null, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); + test('true: padding | false: content | assigned: viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: true, + viewport: document.querySelector('#viewport')!, + content: false, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); - test('null: content | assigned: viewport', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; - }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - viewport: document.querySelector('#viewport')!, - content: null, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); - }); + test('false: padding | assigned: content', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: false, + content: document.querySelector('#content')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); - test('null: content | assigned: padding & viewport', () => { - const snapshot = fillBody(isTextarea, (content, hostId) => { - return `
${content}
`; + test('true: padding | assigned: content', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: true, + content: document.querySelector('#content')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('false: padding | assigned: viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: false, + viewport: document.querySelector('#viewport')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('true: padding | assigned: viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: true, + viewport: document.querySelector('#viewport')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('false: padding | assigned: viewport & content', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + viewport: document.querySelector('#viewport')!, + padding: false, + content: document.querySelector('#content')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('true: padding | assigned: viewport & content', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + viewport: document.querySelector('#viewport')!, + padding: true, + content: document.querySelector('#content')!, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('false: content | assigned: padding', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: document.querySelector('#padding')!, + content: false, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('true: content | assigned: padding', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: document.querySelector('#padding')!, + content: true, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('false: content | assigned: viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + viewport: document.querySelector('#viewport')!, + content: false, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('true: content | assigned: viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + viewport: document.querySelector('#viewport')!, + content: true, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('false: content | assigned: padding & viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: document.querySelector('#padding')!, + viewport: document.querySelector('#viewport')!, + content: false, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); + + test('true: content | assigned: padding & viewport', () => { + const snapshot = fillBody(isTextarea, (content, hostId) => { + return `
${content}
`; + }); + const setup = assertCorrectSetup( + isTextarea, + createStructureSetupProxy({ + host: document.querySelector('#host')!, + target: getTarget(isTextarea), + padding: document.querySelector('#padding')!, + viewport: document.querySelector('#viewport')!, + content: true, + }), + currEnv + ); + assertCorrectDOMStructure(isTextarea); + assertCorrectDestroy(snapshot, setup); + }); }); - const setup = assertCorrectSetup( - isTextarea, - createStructureSetup({ - host: document.querySelector('#host')!, - target: getTarget(isTextarea), - padding: document.querySelector('#padding')!, - viewport: document.querySelector('#viewport')!, - content: null, - }) - ); - assertCorrectDOMStructure(isTextarea); - assertCorrectDestroy(snapshot, setup); }); }); }); diff --git a/packages/overlayscrollbars/tests/jsdom/support/options/validation.test.ts b/packages/overlayscrollbars/tests/jsdom/support/options/validation.test.ts index 164aad4..a752633 100644 --- a/packages/overlayscrollbars/tests/jsdom/support/options/validation.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/support/options/validation.test.ts @@ -4,18 +4,18 @@ import { assignDeep, isEmptyObject } from 'support/utils'; type TestOptionsObj = { propA: 'propA'; null: null }; type TestOptionsEnum = 'A' | 'B' | 'C'; type TestOptions = { - str?: string; - strArrNull?: string | Array | null; - nullbool?: boolean | null; - nested?: { - num?: number; - switch?: boolean; - abc?: TestOptionsEnum; + str: string; + strArrNull: string | Array | null; + nullbool: boolean | null; + nested: { + num: number; + switch: boolean; + abc: TestOptionsEnum; }; - obj?: TestOptionsObj | null; - abc?: TestOptionsEnum; - arr?: Array; - func?: () => void; + obj: TestOptionsObj | null; + abc: TestOptionsEnum; + arr: Array; + func: () => void; }; const options: TestOptions = { @@ -33,7 +33,7 @@ const options: TestOptions = { func: () => {}, }; -const template: OptionsTemplate> = { +const template: OptionsTemplate = { str: oTypes.string, strArrNull: [oTypes.string, oTypes.array, oTypes.null], nullbool: [oTypes.boolean, oTypes.null], @@ -385,7 +385,7 @@ describe('options validation', () => { }); test('nested object is undefined', () => { - const modifiedOptions = assignDeep({}, options); + const modifiedOptions: Partial = assignDeep({}, options); modifiedOptions.nested = undefined; const result = validateOptions(modifiedOptions, template); const { _validated } = result; @@ -399,7 +399,7 @@ describe('options validation', () => { const { warn } = console; console.warn = jest.fn(); - validateOptions(options, template, {}, true); + validateOptions(options, template, null, 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 }); - validateOptions(modifiedOptions, template, {}, false); + validateOptions(modifiedOptions, template, null, false); expect(console.warn).not.toBeCalled(); console.warn = warn; @@ -421,15 +421,15 @@ describe('options validation', () => { console.warn = jest.fn(); // str must be string - validateOptions(assignDeep({}, options, { str: 1 }), template, {}, true); + validateOptions(assignDeep({}, options, { str: 1 }), template, null, true); expect(console.warn).toBeCalledTimes(1); // abc must be A | B | C - validateOptions(assignDeep({}, options, { abc: 'some string' }), template, {}, true); + validateOptions(assignDeep({}, options, { abc: 'some string' }), template, null, true); expect(console.warn).toBeCalledTimes(2); // everthing OK - validateOptions(assignDeep({}, options, { abc: 'C' }), template, {}, true); + validateOptions(assignDeep({}, options, { abc: 'C' }), template, null, true); expect(console.warn).toBeCalledTimes(2); console.warn = warn; diff --git a/packages/overlayscrollbars/types/lifecycles/lifecycleHub.d.ts b/packages/overlayscrollbars/types/lifecycles/lifecycleHub.d.ts index a106734..ee8ef11 100644 --- a/packages/overlayscrollbars/types/lifecycles/lifecycleHub.d.ts +++ b/packages/overlayscrollbars/types/lifecycles/lifecycleHub.d.ts @@ -1,5 +1,5 @@ -import { XY, TRBL, CacheValues, OptionsValidated } from 'support'; -import { Options } from 'options'; +import { XY, TRBL, CacheValues } from 'support'; +import { OverlayScrollbarsOptions } from 'options'; import { StructureSetup } from 'setups/structureSetup'; import { StyleObject } from 'typings'; export declare type LifecycleCheckOption = (path: string) => LifecycleOptionInfo; @@ -23,11 +23,11 @@ export interface LifecycleUpdateHints extends LifecycleAdaptiveUpdateHints { } export declare type Lifecycle = (updateHints: LifecycleUpdateHints, checkOption: LifecycleCheckOption, force: boolean) => Partial | void; export interface LifecycleHubInstance { - _update(changedOptions?: OptionsValidated | null, force?: boolean): void; + _update(changedOptions?: Partial | null, force?: boolean): void; _destroy(): void; } export interface LifecycleHub { - _options: Options; + _options: OverlayScrollbarsOptions; _structureSetup: StructureSetup; _doViewportArrange: boolean; _getPaddingInfo(): PaddingInfo; @@ -37,4 +37,4 @@ export interface LifecycleHub { _getViewportOverflowScroll(): XY; _setViewportOverflowScroll(newViewportOverflowScroll: XY): void; } -export declare const createLifecycleHub: (options: Options, structureSetup: StructureSetup) => LifecycleHubInstance; +export declare const createLifecycleHub: (options: OverlayScrollbarsOptions, structureSetup: StructureSetup) => LifecycleHubInstance; diff --git a/packages/overlayscrollbars/types/options/options.d.ts b/packages/overlayscrollbars/types/options/options.d.ts index 832c889..72e865c 100644 --- a/packages/overlayscrollbars/types/options/options.d.ts +++ b/packages/overlayscrollbars/types/options/options.d.ts @@ -1,2 +1,2 @@ -import { Options } from 'options'; -export declare const optionsTemplate: import("../support/options").OptionsTemplate>, defaultOptions: Required; +import { OverlayScrollbarsOptions } from 'options'; +export declare const optionsTemplate: import("../support/options").OptionsTemplate>, defaultOptions: Required; diff --git a/packages/overlayscrollbars/types/overlayscrollbars/OverlayScrollbars.d.ts b/packages/overlayscrollbars/types/overlayscrollbars/OverlayScrollbars.d.ts index f318e69..b9b8e0c 100644 --- a/packages/overlayscrollbars/types/overlayscrollbars/OverlayScrollbars.d.ts +++ b/packages/overlayscrollbars/types/overlayscrollbars/OverlayScrollbars.d.ts @@ -1,4 +1,4 @@ import { OSTarget, OSTargetObject } from 'typings'; -import { Options } from 'options'; -declare const OverlayScrollbars: (target: OSTarget | OSTargetObject, options?: Options | undefined, extensions?: any) => any; +import { OverlayScrollbarsOptions } from 'options'; +declare const OverlayScrollbars: (target: OSTarget | OSTargetObject, options?: OverlayScrollbarsOptions | undefined, extensions?: any) => any; export { OverlayScrollbars };