mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-09 17:32:27 +03:00
add lifecycleBase unit tests
This commit is contained in:
@@ -8,6 +8,8 @@ import {
|
||||
validateOptions,
|
||||
assignDeep,
|
||||
createCache,
|
||||
isEmptyObject,
|
||||
isBoolean,
|
||||
} from 'support';
|
||||
import { PlainObject } from 'typings';
|
||||
|
||||
@@ -44,17 +46,27 @@ export const createLifecycleBase = <O, C>(
|
||||
updateFunction: (changedOptions: OptionsValidated<O>, changedCache: CacheUpdated<C>) => any
|
||||
): LifecycleBase<O, C> => {
|
||||
const { _template: optionsTemplate, _options: defaultOptions } = transformOptions<Required<O>>(defaultOptionsWithTemplate);
|
||||
const options: Required<O> = assignDeep({}, defaultOptions, validateOptions<O>(initialOptions || ({} as O), optionsTemplate)._validated);
|
||||
const options: Required<O> = assignDeep(
|
||||
{},
|
||||
defaultOptions,
|
||||
validateOptions<O>(initialOptions || ({} as O), optionsTemplate, null, true)._validated
|
||||
);
|
||||
const cacheChange = createCache<C>(cacheUpdateInfo);
|
||||
|
||||
const update = (hints: LifecycleUpdateHints<O, C>) => {
|
||||
const hasForce = isBoolean(hints._force);
|
||||
const force = hints._force === true;
|
||||
const changedCache = cacheChange(force ? undefined : hints._changedCache, force);
|
||||
const changedOptions = force ? ({} as O) : hints._changedOptions || ({} as O);
|
||||
|
||||
updateFunction(changedOptions, changedCache);
|
||||
const changedCache = cacheChange(force ? null : hints._changedCache || (hasForce ? null : []), force);
|
||||
const changedOptions = force ? options : hints._changedOptions || ({} as O);
|
||||
|
||||
if (!isEmptyObject(changedOptions) || !isEmptyObject(changedCache)) {
|
||||
updateFunction(changedOptions, changedCache);
|
||||
}
|
||||
};
|
||||
|
||||
update({ _force: true });
|
||||
|
||||
return {
|
||||
_options(newOptions?: O) {
|
||||
if (newOptions) {
|
||||
@@ -66,7 +78,7 @@ export const createLifecycleBase = <O, C>(
|
||||
return options;
|
||||
},
|
||||
_update: (force?: boolean) => {
|
||||
update({ _force: force });
|
||||
update({ _force: !!force });
|
||||
},
|
||||
_cacheChange: (cachePropsToUpdate?: CachePropsToUpdate<C>) => {
|
||||
update({ _changedCache: cachePropsToUpdate });
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ type EqualCachePropFunction<T, P extends keyof T> = (a?: T[P], b?: T[P]) => bool
|
||||
|
||||
export type CachePropsToUpdate<T> = Array<keyof T> | keyof T;
|
||||
|
||||
export type CacheUpdate<T> = (propsToUpdate?: CachePropsToUpdate<T>, force?: boolean) => CacheUpdated<T>;
|
||||
export type CacheUpdate<T> = (propsToUpdate?: CachePropsToUpdate<T> | null, force?: boolean) => CacheUpdated<T>;
|
||||
|
||||
export type CacheUpdated<T> = {
|
||||
[P in keyof T]?: T[P];
|
||||
@@ -77,7 +77,7 @@ export const createCache = <T>(cacheUpdateInfo: CacheUpdateInfo<T>): CacheUpdate
|
||||
return result;
|
||||
};
|
||||
|
||||
return (propsToUpdate?: CachePropsToUpdate<T>, force?: boolean) => {
|
||||
return (propsToUpdate, force) => {
|
||||
const finalPropsToUpdate: Array<keyof T> =
|
||||
(isString(propsToUpdate) ? ([propsToUpdate] as Array<keyof T>) : (propsToUpdate as Array<keyof T>)) || allProps;
|
||||
each(finalPropsToUpdate, (prop) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { each, indexOf, hasOwnProperty, keys } from 'support/utils';
|
||||
import { each, hasOwnProperty, keys } from 'support/utils';
|
||||
import { type, isArray, isUndefined, isEmptyObject, isPlainObject, isString } from 'support/utils/types';
|
||||
import { OptionsTemplate, OptionsTemplateTypes, OptionsTemplateType, Func, OptionsValidationResult, OptionsValidated } from 'support/options';
|
||||
import { PlainObject } from 'typings';
|
||||
@@ -80,7 +80,13 @@ const validateRecursive = <T extends PlainObject>(
|
||||
|
||||
each(templateValueArr, (currTemplateType) => {
|
||||
// if currType value isn't inside possibleTemplateTypes we assume its a enum string value
|
||||
const isEnumString = indexOf(Object.values(optionsTemplateTypes), currTemplateType) < 0;
|
||||
let typeString: string | undefined;
|
||||
each(optionsTemplateTypes, (value: string, key: string) => {
|
||||
if (value === currTemplateType) {
|
||||
typeString = key;
|
||||
}
|
||||
});
|
||||
const isEnumString = typeString === undefined;
|
||||
if (isEnumString && isString(optionsValue)) {
|
||||
// split it into a array which contains all possible values for example: ["yes", "no", "maybe"]
|
||||
const enumStringSplit = currTemplateType.split(' ');
|
||||
@@ -93,7 +99,7 @@ const validateRecursive = <T extends PlainObject>(
|
||||
}
|
||||
|
||||
// build error message
|
||||
errorPossibleTypes.push(isEnumString ? optionsTemplateTypes.string : currTemplateType);
|
||||
errorPossibleTypes.push(isEnumString ? optionsTemplateTypes.string : typeString!);
|
||||
|
||||
// continue if invalid, break if valid
|
||||
return !isValid;
|
||||
@@ -143,7 +149,7 @@ const validateRecursive = <T extends PlainObject>(
|
||||
const validateOptions = <T extends PlainObject>(
|
||||
options: T,
|
||||
template: OptionsTemplate<Required<T>>,
|
||||
optionsDiff?: T,
|
||||
optionsDiff?: T | null,
|
||||
doWriteErrors?: boolean
|
||||
): OptionsValidationResult<T> => {
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
import { optionsTemplateTypes as oTypes } from 'support';
|
||||
import { createLifecycleBase } from 'lifecycles/lifecycleBase';
|
||||
|
||||
interface TestLifecycleOptions {
|
||||
number?: number;
|
||||
string?: string;
|
||||
nested?: {
|
||||
boolean?: boolean;
|
||||
number?: number;
|
||||
};
|
||||
}
|
||||
interface TestLifecycleCache {
|
||||
number?: number;
|
||||
constant?: boolean;
|
||||
object?: {
|
||||
string?: string;
|
||||
boolean?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const createLifecycle = (initalOptions?: TestLifecycleOptions, updateFn?: () => any) =>
|
||||
createLifecycleBase<TestLifecycleOptions, TestLifecycleCache>(
|
||||
{
|
||||
number: [0, oTypes.number],
|
||||
string: ['hi', oTypes.string],
|
||||
nested: {
|
||||
boolean: [false, oTypes.boolean],
|
||||
number: [0, oTypes.number],
|
||||
},
|
||||
},
|
||||
{
|
||||
number: (current) => (current || 0) + 1,
|
||||
constant: () => false,
|
||||
object: (current) => ({ string: `${current?.string || ''}hi`, boolean: !current?.boolean }),
|
||||
},
|
||||
initalOptions,
|
||||
updateFn || (() => {})
|
||||
);
|
||||
|
||||
describe('lifecycleBase', () => {
|
||||
describe('options', () => {
|
||||
test('correct default options', () => {
|
||||
const { _options } = createLifecycle();
|
||||
|
||||
const defaultOptions = _options();
|
||||
expect(defaultOptions.number).toBe(0);
|
||||
expect(defaultOptions.string).toBe('hi');
|
||||
expect(defaultOptions.nested?.boolean).toBe(false);
|
||||
expect(defaultOptions.nested?.number).toBe(0);
|
||||
});
|
||||
|
||||
test('correct initial options', () => {
|
||||
const { _options } = createLifecycle({ number: 1, nested: { boolean: true } });
|
||||
|
||||
const initOptions = _options();
|
||||
expect(initOptions.number).toBe(1);
|
||||
expect(initOptions.string).toBe('hi');
|
||||
expect(initOptions.nested?.boolean).toBe(true);
|
||||
expect(initOptions.nested?.number).toBe(0);
|
||||
});
|
||||
|
||||
test('correct options change', () => {
|
||||
const { _options } = createLifecycle();
|
||||
|
||||
const options = _options();
|
||||
expect(options.number).toBe(0);
|
||||
expect(options.string).toBe('hi');
|
||||
expect(options.nested?.boolean).toBe(false);
|
||||
expect(options.nested?.number).toBe(0);
|
||||
|
||||
const changedOptions = _options({ number: 2, nested: { number: 3 } });
|
||||
expect(changedOptions.number).toBe(2);
|
||||
expect(changedOptions.string).toBe('hi');
|
||||
expect(changedOptions.nested?.boolean).toBe(false);
|
||||
expect(changedOptions.nested?.number).toBe(3);
|
||||
});
|
||||
|
||||
test('correct options validation', () => {
|
||||
const originalWarn = console.warn;
|
||||
const mockWarn = jest.fn();
|
||||
console.warn = mockWarn;
|
||||
|
||||
// @ts-ignore
|
||||
const { _options } = createLifecycle({ string: 123 });
|
||||
expect(mockWarn).toBeCalledTimes(1);
|
||||
|
||||
const options = _options();
|
||||
expect(options.string).toBe('hi');
|
||||
|
||||
// @ts-ignore
|
||||
const changedOptions = _options({ number: 'string', nested: null });
|
||||
expect(mockWarn).toBeCalledTimes(2);
|
||||
expect(changedOptions.number).toBe(0);
|
||||
expect(changedOptions.string).toBe('hi');
|
||||
expect(changedOptions.nested?.boolean).toBe(false);
|
||||
expect(changedOptions.nested?.number).toBe(0);
|
||||
|
||||
console.warn = originalWarn;
|
||||
});
|
||||
});
|
||||
|
||||
describe('cache', () => {
|
||||
test('single value cache change', () => {
|
||||
const updateFn = jest.fn();
|
||||
const { _cacheChange } = createLifecycle({}, updateFn);
|
||||
|
||||
_cacheChange('number');
|
||||
expect(updateFn).toBeCalledTimes(2);
|
||||
expect(updateFn).toBeCalledWith({}, { number: 2 });
|
||||
|
||||
_cacheChange('constant');
|
||||
expect(updateFn).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
test('multiple value cache change', () => {
|
||||
const updateFn = jest.fn();
|
||||
const { _cacheChange } = createLifecycle({}, updateFn);
|
||||
|
||||
_cacheChange(['number', 'object']);
|
||||
expect(updateFn).toBeCalledTimes(2);
|
||||
expect(updateFn).toBeCalledWith({}, { number: 2, object: { string: 'hihi', boolean: false } });
|
||||
|
||||
_cacheChange(['number', 'constant']);
|
||||
expect(updateFn).toBeCalledTimes(3);
|
||||
expect(updateFn).toBeCalledWith({}, { number: 3 });
|
||||
|
||||
_cacheChange(['constant']);
|
||||
expect(updateFn).toBeCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
test('initial call', () => {
|
||||
const updateFn = jest.fn();
|
||||
createLifecycle({}, updateFn);
|
||||
|
||||
expect(updateFn).toBeCalledTimes(1);
|
||||
expect(updateFn).toBeCalledWith(
|
||||
{
|
||||
number: 0,
|
||||
string: 'hi',
|
||||
nested: {
|
||||
boolean: false,
|
||||
number: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
number: 1,
|
||||
constant: false,
|
||||
object: {
|
||||
string: 'hi',
|
||||
boolean: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('updates correctly on options change', () => {
|
||||
const updateFn = jest.fn();
|
||||
const { _options } = createLifecycle({}, updateFn);
|
||||
|
||||
_options({ number: 5 });
|
||||
expect(updateFn).toBeCalledTimes(2);
|
||||
expect(updateFn).toBeCalledWith({ number: 5 }, {});
|
||||
|
||||
_options({ number: 5, string: 'test', nested: { number: 3 } });
|
||||
expect(updateFn).toBeCalledTimes(3);
|
||||
expect(updateFn).toBeCalledWith({ string: 'test', nested: { number: 3 } }, {});
|
||||
|
||||
_options({ number: 5, string: 'test', nested: { number: 3 } });
|
||||
expect(updateFn).toBeCalledTimes(3);
|
||||
});
|
||||
|
||||
test('updates correctly on cache change', () => {
|
||||
const updateFn = jest.fn();
|
||||
const { _cacheChange } = createLifecycle({}, updateFn);
|
||||
|
||||
_cacheChange('number');
|
||||
expect(updateFn).toBeCalledTimes(2);
|
||||
expect(updateFn).toBeCalledWith({}, { number: 2 });
|
||||
|
||||
_cacheChange(['number', 'object', 'constant']);
|
||||
expect(updateFn).toBeCalledTimes(3);
|
||||
expect(updateFn).toBeCalledWith({}, { number: 3, object: { string: 'hihi', boolean: false } });
|
||||
|
||||
_cacheChange('constant');
|
||||
expect(updateFn).toBeCalledTimes(3);
|
||||
});
|
||||
|
||||
test('updates correctly on update call', () => {
|
||||
const updateFn = jest.fn();
|
||||
const { _update, _options } = createLifecycle({}, updateFn);
|
||||
|
||||
_update();
|
||||
expect(updateFn).toBeCalledTimes(2);
|
||||
expect(updateFn).toBeCalledWith({}, { number: 2, object: { string: 'hihi', boolean: false } });
|
||||
|
||||
_update(true);
|
||||
expect(updateFn).toBeCalledTimes(3);
|
||||
expect(updateFn).toBeCalledWith(
|
||||
{
|
||||
number: 0,
|
||||
string: 'hi',
|
||||
nested: {
|
||||
boolean: false,
|
||||
number: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
number: 3,
|
||||
constant: false,
|
||||
object: {
|
||||
string: 'hihihi',
|
||||
boolean: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
_options({ number: 3, nested: { boolean: true } });
|
||||
_update(true);
|
||||
expect(updateFn).toBeCalledTimes(5);
|
||||
expect(updateFn).toBeCalledWith(
|
||||
{
|
||||
number: 3,
|
||||
string: 'hi',
|
||||
nested: {
|
||||
boolean: true,
|
||||
number: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
number: 4,
|
||||
constant: false,
|
||||
object: {
|
||||
string: 'hihihihi',
|
||||
boolean: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -201,5 +201,22 @@ describe('cache', () => {
|
||||
expect(updateNumberFn).toHaveBeenCalledWith(2, 1);
|
||||
expect(Object.prototype.hasOwnProperty.call(updateCache('number'), 'number')).toBe(false);
|
||||
});
|
||||
|
||||
test('updates all entries with null or undefined as argument', () => {
|
||||
const [updateNumberFn, updateNumber] = createUpdater<number>((i) => i);
|
||||
const [updateNumberFn2, updateNumber2] = createUpdater<number>((i) => i);
|
||||
const updateCache = createCache({
|
||||
number: updateNumber,
|
||||
number2: updateNumber2,
|
||||
});
|
||||
|
||||
updateCache();
|
||||
expect(updateNumberFn).toHaveBeenCalledWith(undefined, undefined);
|
||||
expect(updateNumberFn2).toHaveBeenCalledWith(undefined, undefined);
|
||||
|
||||
updateCache(null);
|
||||
expect(updateNumberFn).toHaveBeenCalledWith(1, undefined);
|
||||
expect(updateNumberFn2).toHaveBeenCalledWith(1, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user