From 9504df222ccbf768a9f962ad360936a166e3138d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandras=20=C5=A0ukelovi=C4=8D?= <64651944+AlexShukel@users.noreply.github.com> Date: Mon, 7 Mar 2022 22:19:35 +0200 Subject: [PATCH] Migrate to path utilities from pxth package (#101) * Migrated to new pxth utilities * Created PxthMap abstraction * Fixed tests * Used getPxthSegments in tests * Removed 'values' function --- package-lock.json | 14 ++-- package.json | 2 +- src/hooks/useMappingProxy.ts | 5 +- src/hooks/useObservers.ts | 65 ++++++--------- src/index.ts | 2 - src/typings/BatchUpdate.ts | 2 +- src/typings/MappingProxy.ts | 48 +++++------ src/typings/ProxyMap.ts | 25 ------ src/typings/PxthMap.ts | 33 ++++++++ src/utils/areProxyMapsEqual.ts | 3 +- src/utils/createProxyMap.ts | 9 +- src/utils/mappingProxyUtils.ts | 16 ++-- src/utils/pathUtils.ts | 122 ---------------------------- src/utils/useInterceptors.ts | 6 +- test/hooks/useObservers.test.ts | 9 +- test/hooks/useStockContext.test.tsx | 7 +- test/typings/MappingProxy.test.ts | 78 +++++++++--------- test/utils/pathUtils.test.ts | 120 --------------------------- test/utils/useInterceptors.test.ts | 14 ++-- 19 files changed, 156 insertions(+), 424 deletions(-) delete mode 100644 src/typings/ProxyMap.ts create mode 100644 src/typings/PxthMap.ts delete mode 100644 src/utils/pathUtils.ts delete mode 100644 test/utils/pathUtils.test.ts diff --git a/package-lock.json b/package-lock.json index 7e3bcdd..74febea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "lodash": "^4.17.20", "lodash-es": "^4.17.15", - "pxth": "^0.4.0", + "pxth": "^0.5.0", "tiny-invariant": "^1.1.0" }, "devDependencies": { @@ -16466,9 +16466,9 @@ } }, "node_modules/pxth": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/pxth/-/pxth-0.4.0.tgz", - "integrity": "sha512-/EzV2oqvEndfdJ2GisbH1wmdZBYx6IxuopK/SRE77HhZr8ysKhY4aVIeYaCn+2r84zSMqO62VC1qaEVz1vcIkw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/pxth/-/pxth-0.5.0.tgz", + "integrity": "sha512-n2cFet92FdTWl0/Chg2UWUWIxAD8raBI/kPzq4uvZBYO5IzkM/ErXn+FBhgAJqqR8L1Kq4+OaeDOYsdptpJCSA==", "dependencies": { "lodash": "^4.17.21", "tiny-invariant": "^1.1.0" @@ -33931,9 +33931,9 @@ } }, "pxth": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/pxth/-/pxth-0.4.0.tgz", - "integrity": "sha512-/EzV2oqvEndfdJ2GisbH1wmdZBYx6IxuopK/SRE77HhZr8ysKhY4aVIeYaCn+2r84zSMqO62VC1qaEVz1vcIkw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/pxth/-/pxth-0.5.0.tgz", + "integrity": "sha512-n2cFet92FdTWl0/Chg2UWUWIxAD8raBI/kPzq4uvZBYO5IzkM/ErXn+FBhgAJqqR8L1Kq4+OaeDOYsdptpJCSA==", "requires": { "lodash": "^4.17.21", "tiny-invariant": "^1.1.0" diff --git a/package.json b/package.json index a762751..71634ee 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "dependencies": { "lodash": "^4.17.20", "lodash-es": "^4.17.15", - "pxth": "^0.4.0", + "pxth": "^0.5.0", "tiny-invariant": "^1.1.0" } } diff --git a/src/hooks/useMappingProxy.ts b/src/hooks/useMappingProxy.ts index b1ef727..3f09eb6 100644 --- a/src/hooks/useMappingProxy.ts +++ b/src/hooks/useMappingProxy.ts @@ -1,8 +1,9 @@ import { useEffect, useMemo, useRef } from 'react'; -import { Pxth, pxthToString } from 'pxth'; +import { Pxth } from 'pxth'; import { MappingProxy, ProxyMapSource } from '../typings'; import { areProxyMapsEqual } from '../utils/areProxyMapsEqual'; +import { hashPxth } from '../utils/hashPxth'; const useMapMemo = (map: ProxyMapSource): ProxyMapSource => { const mapRef = useRef(map); @@ -23,5 +24,5 @@ export const useMappingProxy = (mapSource: ProxyMapSource, path: Pxth) const proxy = new MappingProxy(realMap, path); proxy.activate(); return proxy; - }, [pxthToString(path), realMap]); + }, [hashPxth(path), realMap]); }; diff --git a/src/hooks/useObservers.ts b/src/hooks/useObservers.ts index 66066d2..ef27304 100644 --- a/src/hooks/useObservers.ts +++ b/src/hooks/useObservers.ts @@ -1,10 +1,10 @@ import { useCallback, useRef } from 'react'; -import { createPxth, deepGet, parseSegmentsFromString, Pxth, pxthToString, RootPath } from 'pxth'; +import { createPxth, deepGet, isInnerPxth, Pxth, samePxth } from 'pxth'; import invariant from 'tiny-invariant'; import { BatchUpdate, Observer } from '../typings'; +import { PxthMap } from '../typings/PxthMap'; import { ObserverArray, ObserverKey } from '../utils/ObserverArray'; -import { isInnerPath } from '../utils/pathUtils'; import { useLazyRef } from '../utils/useLazyRef'; export type ObserversControl = { @@ -24,19 +24,9 @@ export type ObserversControl = { /** Hook, wraps functionality of observers storage (add, remove, notify tree of observers, etc.) */ export const useObservers = (): ObserversControl => { - const observers = useRef>>( - {} as Record> - ); + const observersMap = useRef>>(new PxthMap()); const batchUpdateObservers = useLazyRef>>(() => new ObserverArray()); - const getObserversKeys = useCallback( - () => [ - ...Object.keys(observers.current), - ...(Object.getOwnPropertySymbols(observers.current) as unknown as string[]), - ], - [] - ); - const batchUpdate = useCallback( (update: BatchUpdate) => { batchUpdateObservers.current.call(update); @@ -55,25 +45,23 @@ export const useObservers = (): ObserversControl => { ); const observe = useCallback((path: Pxth, observer: Observer) => { - const pathKey = pxthToString(path); - - if (!Object.prototype.hasOwnProperty.call(observers.current, pathKey)) { - observers.current[pathKey] = new ObserverArray(); + if (!observersMap.current.has(path)) { + observersMap.current.set(path, new ObserverArray()); } - return observers.current[pathKey].add(observer as Observer); + return observersMap.current.get(path).add(observer as Observer); }, []); const stopObserving = useCallback((path: Pxth, observerKey: ObserverKey) => { - const pathKey = pxthToString(path); - - const currentObservers = observers.current[pathKey]; + const currentObservers = observersMap.current.get(path); invariant(currentObservers, 'Cannot remove observer from value, which is not observing'); currentObservers.remove(observerKey); - if (currentObservers.isEmpty()) delete observers.current[pathKey]; + if (currentObservers.isEmpty()) { + observersMap.current.remove(path); + } }, []); const watch = useCallback( @@ -95,17 +83,14 @@ export const useObservers = (): ObserversControl => { [observeBatchUpdates, stopObservingBatchUpdates] ); - const isObserved = useCallback( - (path: Pxth) => Object.prototype.hasOwnProperty.call(observers.current, pxthToString(path)), - [] - ); + const isObserved = useCallback((path: Pxth) => observersMap.current.has(path), []); const notifyPaths = useCallback( - (origin: Pxth, paths: string[], values: T) => { + (origin: Pxth, paths: Pxth[], values: T) => { batchUpdate({ paths, origin, values }); paths.forEach(path => { - const observer = observers.current[path]; - const subValue = deepGet(values, createPxth(parseSegmentsFromString(path))); + const observer = observersMap.current.get(path); + const subValue = deepGet(values, path); observer.call(subValue); }); }, @@ -114,23 +99,23 @@ export const useObservers = (): ObserversControl => { const notifySubTree = useCallback( (path: Pxth, values: T) => { - const stringifiedPath = pxthToString(path); - - const subPaths = getObserversKeys().filter( - tempPath => - isInnerPath(stringifiedPath, tempPath) || - stringifiedPath === tempPath || - isInnerPath(tempPath, stringifiedPath) - ); + const subPaths = observersMap.current + .keys() + .filter( + tempPath => + isInnerPxth(path as Pxth, tempPath) || + samePxth(path as Pxth, tempPath) || + isInnerPxth(tempPath, path as Pxth) + ); notifyPaths(path as Pxth, subPaths, values); }, - [notifyPaths, getObserversKeys] + [notifyPaths] ); const notifyAll = useCallback( - (values: T) => notifyPaths(createPxth([]), getObserversKeys(), values), - [notifyPaths, getObserversKeys] + (values: T) => notifyPaths(createPxth([]), observersMap.current.keys(), values), + [notifyPaths] ); return { diff --git a/src/index.ts b/src/index.ts index 23df3aa..9c7266d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,5 +4,3 @@ export * from './components'; export * from './utils/ObserverArray'; export { intercept } from './utils/useInterceptors'; - -export * from './utils/pathUtils'; diff --git a/src/typings/BatchUpdate.ts b/src/typings/BatchUpdate.ts index 48ac903..a7bcca6 100644 --- a/src/typings/BatchUpdate.ts +++ b/src/typings/BatchUpdate.ts @@ -3,7 +3,7 @@ import { Pxth } from 'pxth'; /** Object, in which "stocked" calls observers */ export type BatchUpdate = { /** which paths should be updated */ - paths: string[]; + paths: Pxth[]; /** path, which triggered subtree update */ origin: Pxth; /** all values, which should be sent to observers */ diff --git a/src/typings/MappingProxy.ts b/src/typings/MappingProxy.ts index daaea8f..53287e8 100644 --- a/src/typings/MappingProxy.ts +++ b/src/typings/MappingProxy.ts @@ -1,14 +1,13 @@ import isNil from 'lodash/isNil'; -import { createPxth, deepGet, deepSet, parseSegmentsFromString, Pxth, pxthToString } from 'pxth'; +import { deepGet, deepSet, isInnerPxth, joinPxths, longestCommonPxth, Pxth, relativePxth, samePxth } from 'pxth'; import invariant from 'tiny-invariant'; import { Observer } from './Observer'; -import { ProxyMap } from './ProxyMap'; +import { PxthMap } from './PxthMap'; import { StockProxy } from './StockProxy'; import { ProxyMapSource } from '.'; import { createProxyMap } from '../utils/createProxyMap'; import { getInnerPaths, hasMappedParentPaths } from '../utils/mappingProxyUtils'; -import { isInnerPath, joinPaths, longestCommonPath, relativePath, samePxth } from '../utils/pathUtils'; /** * Simple example of StockProxy. @@ -22,7 +21,7 @@ import { isInnerPath, joinPaths, longestCommonPath, relativePath, samePxth } fro * } */ export class MappingProxy extends StockProxy { - private readonly proxyMap: ProxyMap; + private readonly proxyMap: PxthMap>; public constructor(mapSource: ProxyMapSource, path: Pxth) { super(path); @@ -30,7 +29,7 @@ export class MappingProxy extends StockProxy { } public setValue = (path: Pxth, value: V, defaultSetValue: (path: Pxth, value: U) => void) => { - const relativeValuePath = relativePath(this.path, path); + const relativeValuePath = relativePxth(this.path as Pxth, path as Pxth); if (hasMappedParentPaths(relativeValuePath, this.proxyMap)) { const normalPath = this.getNormalPath(path); @@ -42,7 +41,7 @@ export class MappingProxy extends StockProxy { innerPaths.forEach( ([to, from]) => - from !== undefined && defaultSetValue(from, deepGet(value, relativePath(relativeValuePath, to))) + from !== undefined && defaultSetValue(from, deepGet(value, relativePxth(relativeValuePath, to))) ); }; @@ -61,7 +60,7 @@ export class MappingProxy extends StockProxy { }; private mapValue = (value: V, path: Pxth, normalPath: Pxth): V => { - path = relativePath(this.path, path); + path = relativePxth(this.path as Pxth, path as Pxth) as Pxth; if (hasMappedParentPaths(path, this.proxyMap)) { return value; @@ -73,8 +72,8 @@ export class MappingProxy extends StockProxy { (acc, [to, from]) => deepSet( acc as unknown as object, - relativePath(path, to), - deepGet(value, relativePath(normalPath, from!)) + relativePxth(path as Pxth, to), + deepGet(value, relativePxth(normalPath as Pxth, from!)) ) as V, {} as V ); @@ -88,12 +87,11 @@ export class MappingProxy extends StockProxy { 'Mapping proxy error: trying to get normal path of proxied path, which is not defined in proxy map' ); - return joinPaths(this.path as Pxth, normalPath); + return joinPxths(this.path as Pxth, normalPath) as Pxth; }; public getNormalPath = (path: Pxth): Pxth => { - const normalPath = relativePath(this.path, path); - const stringifiedPath = pxthToString(normalPath); + const normalPath = relativePxth(this.path as Pxth, path as Pxth); const isIndependent = this.proxyMap.has(normalPath); @@ -101,29 +99,21 @@ export class MappingProxy extends StockProxy { return this.proxyMap.get(normalPath) as Pxth; } - const hasMappedChildrenPaths = this.proxyMap - .entries() - .some(([mappedPath]) => isInnerPath(stringifiedPath, pxthToString(mappedPath))); + const hasMappedChildrenPaths = this.proxyMap.keys().some(mappedPath => isInnerPxth(normalPath, mappedPath)); if (hasMappedChildrenPaths) { - return createPxth( - parseSegmentsFromString( - longestCommonPath( - this.proxyMap - .entries() - .filter(([to]) => isInnerPath(stringifiedPath, pxthToString(to))) - .map(([, from]) => pxthToString(from!) as string) - ) - ) - ); + return longestCommonPxth( + this.proxyMap + .entries() + .filter(([to]) => isInnerPxth(normalPath, to)) + .map(([, from]) => from) + ) as Pxth; } if (hasMappedParentPaths(normalPath, this.proxyMap)) { - const [to, from] = this.proxyMap - .entries() - .find(([mappedPath]) => isInnerPath(pxthToString(mappedPath), stringifiedPath))!; + const [to, from] = this.proxyMap.entries().find(([mappedPath]) => isInnerPxth(mappedPath, normalPath))!; - return joinPaths(from!, relativePath(to, normalPath as Pxth)); + return joinPxths(from!, relativePxth(to, normalPath)) as Pxth; } invariant(false, 'Mapping proxy error: trying to proxy value, which is not defined in proxy map.'); diff --git a/src/typings/ProxyMap.ts b/src/typings/ProxyMap.ts deleted file mode 100644 index c454c14..0000000 --- a/src/typings/ProxyMap.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Pxth } from 'pxth'; - -import { hashPxth } from '../utils/hashPxth'; - -export class ProxyMap { - private values: Record> = {}; - private keys: Record> = {}; - - public get = (key: Pxth) => { - return this.values[hashPxth(key)]; - }; - - public set = (key: Pxth, value: Pxth) => { - const stingifiedKey = hashPxth(key); - this.values[stingifiedKey] = value as Pxth; - this.keys[stingifiedKey] = key as Pxth; - }; - - public has = (key: Pxth) => { - return hashPxth(key) in this.values; - }; - - public entries = (): Array<[key: Pxth, value: Pxth]> => - Object.entries(this.keys).map(([key, value]) => [value, this.values[key]]); -} diff --git a/src/typings/PxthMap.ts b/src/typings/PxthMap.ts new file mode 100644 index 0000000..1fa6c01 --- /dev/null +++ b/src/typings/PxthMap.ts @@ -0,0 +1,33 @@ +import { Pxth } from 'pxth'; + +import { hashPxth } from '../utils/hashPxth'; + +export class PxthMap { + private _values: Record = {}; + private _keys: Record> = {}; + + public get = (key: Pxth): Value => { + return this._values[hashPxth(key)]; + }; + + public set = (key: Pxth, value: Value) => { + const stingifiedKey = hashPxth(key); + this._values[stingifiedKey] = value; + this._keys[stingifiedKey] = key as Pxth; + }; + + public remove = (key: Pxth) => { + const stingifiedKey = hashPxth(key); + delete this._values[stingifiedKey]; + delete this._keys[stingifiedKey]; + }; + + public has = (key: Pxth) => { + return hashPxth(key) in this._values; + }; + + public keys = (): Pxth[] => Object.values(this._keys); + + public entries = (): Array<[key: Pxth, value: Value]> => + Object.entries(this._keys).map(([key, value]) => [value, this._values[key]]); +} diff --git a/src/utils/areProxyMapsEqual.ts b/src/utils/areProxyMapsEqual.ts index 7f40b72..bc7f89b 100644 --- a/src/utils/areProxyMapsEqual.ts +++ b/src/utils/areProxyMapsEqual.ts @@ -1,5 +1,6 @@ +import { samePxth } from 'pxth'; + import { createProxyMap } from './createProxyMap'; -import { samePxth } from './pathUtils'; import { ProxyMapSource } from '../typings/ProxyMapSource'; export const areProxyMapsEqual = (map1: ProxyMapSource, map2: ProxyMapSource) => { diff --git a/src/utils/createProxyMap.ts b/src/utils/createProxyMap.ts index bb71f08..6c2c1a8 100644 --- a/src/utils/createProxyMap.ts +++ b/src/utils/createProxyMap.ts @@ -1,14 +1,13 @@ -import { createPxth, isPxth, Pxth } from 'pxth'; +import { createPxth, isPxth, joinPxths, Pxth } from 'pxth'; import invariant from 'tiny-invariant'; -import { joinPaths } from './pathUtils'; -import { ProxyMap } from '../typings/ProxyMap'; import { ProxyMapSource } from '../typings/ProxyMapSource'; +import { PxthMap } from '../typings/PxthMap'; const getAllObjectKeys = (obj: object) => [...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)]; export const createProxyMap = (mapSource: ProxyMapSource) => { - const proxyMap = new ProxyMap(); + const proxyMap = new PxthMap>(); if (isPxth(mapSource)) { proxyMap.set(createPxth([]), mapSource); @@ -23,7 +22,7 @@ export const createProxyMap = (mapSource: ProxyMapSource) => { for (const key of getAllObjectKeys(innerObject)) { const item = innerObject[key] as Pxth | ProxyMapSource; - const pathToItem = joinPaths(pathToObject, createPxth([key])); + const pathToItem = joinPxths(pathToObject, createPxth([key])); if (isPxth(item)) { proxyMap.set(pathToItem, item); diff --git a/src/utils/mappingProxyUtils.ts b/src/utils/mappingProxyUtils.ts index 75f013a..bf7ff36 100644 --- a/src/utils/mappingProxyUtils.ts +++ b/src/utils/mappingProxyUtils.ts @@ -1,17 +1,13 @@ -import { Pxth, pxthToString } from 'pxth'; +import { isInnerPxth, Pxth, samePxth } from 'pxth'; -import { isInnerPath } from './pathUtils'; -import { ProxyMap } from '../typings/ProxyMap'; +import { PxthMap } from '../typings/PxthMap'; -export const getInnerPaths = (path: Pxth, proxyMap: ProxyMap) => { - const stringifiedPath = pxthToString(path); +export const getInnerPaths = (path: Pxth, proxyMap: PxthMap>) => { return proxyMap.entries().filter(([to]) => { - const stringifiedTo = pxthToString(to); - return isInnerPath(stringifiedPath, stringifiedTo) || stringifiedPath === stringifiedTo; + return isInnerPxth(path as Pxth, to) || samePxth(path as Pxth, to); }); }; -export const hasMappedParentPaths = (path: Pxth, proxyMap: ProxyMap) => { - const stringifiedPath = pxthToString(path); - return proxyMap.entries().some(([mappedPath]) => isInnerPath(pxthToString(mappedPath), stringifiedPath)); +export const hasMappedParentPaths = (path: Pxth, proxyMap: PxthMap>) => { + return proxyMap.entries().some(([mappedPath]) => isInnerPxth(mappedPath, path as Pxth)); }; diff --git a/src/utils/pathUtils.ts b/src/utils/pathUtils.ts deleted file mode 100644 index 61db4ec..0000000 --- a/src/utils/pathUtils.ts +++ /dev/null @@ -1,122 +0,0 @@ -import toPath from 'lodash/toPath'; -import { - createPxth, - getPxthSegments, - parseSegmentsFromString, - Pxth, - pxthToString, - RootPath, - RootPathToken, -} from 'pxth'; -import invariant from 'tiny-invariant'; - -export const samePxth = (pxth1: Pxth, pxth2: Pxth): boolean => { - const segments1 = getPxthSegments(pxth1); - const segments2 = getPxthSegments(pxth2); - - if (segments1.length !== segments2.length) { - return false; - } - - for (let i = 0; i < segments1.length; i++) { - if (segments1[i] !== segments2[i]) { - return false; - } - } - - return true; -}; - -export const joinPaths = (...segments: Pxth[]): Pxth => { - if (segments.length === 0) { - return createPxth([]); - } - - return createPxth(segments.map(getPxthSegments).flat()); -}; - -/** - * Function, which normalizes path. - * - * @example - * - * array[0].value.1.child -> array.0.value.1.child - * path["to"][0].variable["yes"] -> path.to.0.variable.yes - * - * @param path - path to normalize - */ -export function normalizePath(path: string): string; -export function normalizePath(path: RootPath): RootPath; - -export function normalizePath(path: string | RootPath) { - return path === RootPathToken ? path : toPath(path).join('.').trim(); -} - -/** - * Function, which indicates, if path is child of another or not. - * - * @example - * - * isInnerPath('parent', 'parent.child') -> true - * isInnerPath('notParent', 'parent.child') -> false - * - * @param _basePath - path, which is probably parent - * @param _path - path, which is probably child of _basePath - */ -export const isInnerPath = (_basePath: string | RootPath, _path: string | RootPath) => { - if (_basePath === RootPathToken) return true; - if (_path === RootPathToken) return false; - const path = normalizePath(_path); - const basePath = normalizePath(_basePath); - return path.indexOf(basePath + '.') === 0 && path.replace(basePath, '').trim().length > 0; -}; - -/** - * Finds longest common path. - * @example - * ['hello.world', 'hello.world.yes', 'hello.world.bye.asdf'] -> 'hello.world' - * ['a', 'b', 'c'] -> ROOT_PATH - */ -export const longestCommonPath = (paths: string[]): string | RootPath => { - if (paths.length === 0) return RootPathToken; - if (paths.length === 1) return normalizePath(paths[0]); - const sortedPaths = paths.sort(); - const firstPath = toPath(sortedPaths[0]); - const lastPath = toPath(sortedPaths[sortedPaths.length - 1]); - for (let i = 0; i < firstPath.length; i++) { - if (firstPath[i] !== lastPath[i]) { - return firstPath.slice(0, i).join('.') || RootPathToken; - } - } - return firstPath.join('.'); -}; - -/** - * Returns relative path. If subPath is not child of basePath, it will throw an error. - * @example - * relativePath('hello.world', 'hello.world.asdf') -> 'asdf', - * relativePath('a.b.c', 'a.b.c.d.e') -> 'd.e', - * relativePath('a', 'b') -> Error - */ -export const relativePath = (_basePath: Pxth, _subPath: Pxth): Pxth => { - const basePath = pxthToString(_basePath); - const subPath = pxthToString(_subPath); - - if (basePath === RootPathToken && subPath === RootPathToken) { - return createPxth([]); - } - - if (basePath === RootPathToken) { - return _subPath; - } - - invariant(subPath !== RootPathToken, `ROOT_PATH symbol cannot be sub path of any path ("${basePath}")`); - - invariant(basePath.length > 0 && subPath.indexOf(basePath) === 0, `"${subPath}" is not sub path of "${basePath}"`); - - if (basePath === subPath) { - return createPxth([]); - } else { - return createPxth(parseSegmentsFromString(subPath.replace(basePath + '.', ''))); - } -}; diff --git a/src/utils/useInterceptors.ts b/src/utils/useInterceptors.ts index ee2cc91..4a2f4dc 100644 --- a/src/utils/useInterceptors.ts +++ b/src/utils/useInterceptors.ts @@ -1,17 +1,15 @@ import { useCallback, useEffect } from 'react'; import cloneDeep from 'lodash/cloneDeep'; import unset from 'lodash/unset'; -import { deepGet, deepSet, getPxthSegments, Pxth, pxthToString } from 'pxth'; +import { deepGet, deepSet, getPxthSegments, isInnerPxth, Pxth, samePxth } from 'pxth'; import invariant from 'tiny-invariant'; -import { isInnerPath } from './pathUtils'; import { Stock } from '../hooks/useStock'; import { Observer } from '../typings'; import { StockProxy } from '../typings/StockProxy'; const shouldUseProxy = (proxy: StockProxy | undefined, path: Pxth) => - proxy && - (isInnerPath(pxthToString(proxy.path), pxthToString(path)) || pxthToString(proxy.path) === pxthToString(path)); + proxy && (isInnerPxth(proxy.path, path) || samePxth(proxy.path, path)); /** * Helper function. Calls `standardCallback` if `proxy` is undefined, or if `path` isn't inner path of `proxy.path` variable. diff --git a/test/hooks/useObservers.test.ts b/test/hooks/useObservers.test.ts index a6d1452..5318f29 100644 --- a/test/hooks/useObservers.test.ts +++ b/test/hooks/useObservers.test.ts @@ -1,5 +1,5 @@ import { act, renderHook } from '@testing-library/react-hooks'; -import { createPxth, pxthToString } from 'pxth'; +import { createPxth, getPxthSegments } from 'pxth'; import { useObservers } from '../../src'; @@ -251,12 +251,11 @@ describe('Batch observers tests', () => { }); expect(observer).toBeCalled(); - expect(pxthToString(observer.mock.calls[0][0].origin)).toBe( - pxthToString(createPxth(['parent', 'child', 'hello'])) - ); + expect(getPxthSegments(observer.mock.calls[0][0].origin)).toStrictEqual(['parent', 'child', 'hello']); + expect(observer.mock.calls[0][0].paths.map(getPxthSegments)).toStrictEqual([['parent', 'child'], ['parent']]); expect(observer).toBeCalledWith({ origin: expect.anything(), - paths: ['parent.child', 'parent'], + paths: expect.anything(), values: { value: 'asdf', parent: { diff --git a/test/hooks/useStockContext.test.tsx b/test/hooks/useStockContext.test.tsx index 36c7472..8216d77 100644 --- a/test/hooks/useStockContext.test.tsx +++ b/test/hooks/useStockContext.test.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { act, renderHook } from '@testing-library/react-hooks'; -import { createPxth } from 'pxth'; -import { pxthToString } from 'pxth'; +import { createPxth, getPxthSegments } from 'pxth'; import { StockContext, useStock, useStockContext } from '../../src'; import { ProxyContext } from '../../src/components/ProxyContext'; @@ -66,7 +65,7 @@ describe('Test "useStockContext" hook', () => { result.current.watch(createPxth(['aaaa']), () => {}); }); - expect(pxthToString(watch.mock.calls[watch.mock.calls.length - 1][0])).toBe(pxthToString(createPxth(['asdf']))); + expect(getPxthSegments(watch.mock.calls[watch.mock.calls.length - 1][0])).toStrictEqual(['asdf']); expect(watch).lastCalledWith(expect.anything(), observer, expect.any(Function)); }); @@ -95,7 +94,7 @@ describe('Test "useStockContext" hook', () => { result.current.watch(createPxth(['aaaa']), () => {}); }); - expect(pxthToString(watch.mock.calls[watch.mock.calls.length - 1][0])).toBe(pxthToString(createPxth(['asdf']))); + expect(getPxthSegments(watch.mock.calls[watch.mock.calls.length - 1][0])).toStrictEqual(['asdf']); expect(watch).lastCalledWith(expect.anything(), observer, expect.any(Function)); }); }); diff --git a/test/typings/MappingProxy.test.ts b/test/typings/MappingProxy.test.ts index 58332dd..78a02d2 100644 --- a/test/typings/MappingProxy.test.ts +++ b/test/typings/MappingProxy.test.ts @@ -1,4 +1,4 @@ -import { createPxth, deepGet, deepSet, Pxth, pxthToString } from 'pxth'; +import { createPxth, deepGet, deepSet, getPxthSegments, Pxth, samePxth } from 'pxth'; import { MappingProxy, Observer, ProxyMapSource } from '../../src/typings'; @@ -44,12 +44,12 @@ describe('Mapping proxy', () => { proxy.watch(createPxth(['asdf', 'hello']), observer, defaultObserve); - expect(pxthToString(defaultObserve.mock.calls[0][0])).toBe(pxthToString(createPxth(['a', 'b', 'c']))); + expect(getPxthSegments(defaultObserve.mock.calls[0][0])).toStrictEqual(['a', 'b', 'c']); defaultObserve.mockClear(); proxy.watch(createPxth(['asdf', 'bye']), observer, defaultObserve); - expect(pxthToString(defaultObserve.mock.calls[0][0])).toBe(pxthToString(createPxth(['a', 'b', 'd']))); + expect(getPxthSegments(defaultObserve.mock.calls[0][0])).toStrictEqual(['a', 'b', 'd']); }); it('observe/stopObserving (empty mapping path)', () => { @@ -61,7 +61,7 @@ describe('Mapping proxy', () => { defaultObserve.mockReturnValue(0); proxy.watch(createPxth(['asdf']), observer, defaultObserve); - expect(pxthToString(defaultObserve.mock.calls[0][0])).toBe(pxthToString(createPxth(['a', 'd', 'c']))); + expect(getPxthSegments(defaultObserve.mock.calls[0][0])).toStrictEqual(['a', 'd', 'c']); defaultObserve.mockClear(); }); @@ -78,12 +78,12 @@ describe('Mapping proxy', () => { defaultObserve.mockReturnValue(0); proxy.watch(createPxth(['hello']), observer, defaultObserve); - expect(pxthToString(defaultObserve.mock.calls[0][0])).toBe(pxthToString(createPxth(['a', 'd', 'c']))); + expect(getPxthSegments(defaultObserve.mock.calls[0][0])).toStrictEqual(['a', 'd', 'c']); defaultObserve.mockClear(); proxy.watch(createPxth(['bye']), observer, defaultObserve); - expect(pxthToString(defaultObserve.mock.calls[0][0])).toBe(pxthToString(createPxth(['b', 'b', 'd']))); + expect(getPxthSegments(defaultObserve.mock.calls[0][0])).toStrictEqual(['b', 'b', 'd']); }); it('calling observer fns', () => { @@ -121,19 +121,17 @@ describe('Mapping proxy', () => { proxy.watch(createPxth(['registeredUser', 'personalData', 'name', 'firstName']), observer, defaultObserve); - expect(pxthToString(defaultObserve.mock.calls[0][0])).toBe( - pxthToString(createPxth(['registeredUser', 'name'])) - ); + expect(getPxthSegments(defaultObserve.mock.calls[0][0])).toStrictEqual(['registeredUser', 'name']); defaultObserve.mockClear(); proxy.watch(createPxth(['registeredUser', 'personalData', 'name']), observer, defaultObserve); - expect(pxthToString(defaultObserve.mock.calls[0][0])).toBe(pxthToString(createPxth(['registeredUser']))); + expect(getPxthSegments(defaultObserve.mock.calls[0][0])).toStrictEqual(['registeredUser']); defaultObserve.mockClear(); proxy.watch(createPxth(['registeredUser', 'personalData']), observer, defaultObserve); - expect(pxthToString(defaultObserve.mock.calls[0][0])).toBe(pxthToString(createPxth([]))); + expect(getPxthSegments(defaultObserve.mock.calls[0][0])).toStrictEqual([]); observers[0](rawData.registeredUser.name); @@ -232,12 +230,12 @@ describe('Mapping proxy', () => { const observer = jest.fn(); proxy.watch(createPxth(['truck', 'owner']), observer, defaultWatch); - expect(pxthToString(defaultWatch.mock.calls[0][0])).toBe(pxthToString(createPxth([]))); + expect(getPxthSegments(defaultWatch.mock.calls[0][0])).toStrictEqual([]); defaultWatch.mockClear(); proxy.watch(createPxth(['truck', 'info']), observer, defaultWatch); - expect(pxthToString(defaultWatch.mock.calls[0][0])).toBe(pxthToString(createPxth([]))); + expect(getPxthSegments(defaultWatch.mock.calls[0][0])).toStrictEqual([]); observers[0](rawData); expect(observer).toBeCalledWith(fullData.truck.owner.contacts[0]); @@ -255,9 +253,7 @@ describe('Mapping proxy', () => { proxy.setValue(createPxth(['registeredUser', 'personalData', 'name', 'firstName']), 'Hello', defaultSetValue); - expect(pxthToString(defaultSetValue.mock.calls[0][0])).toBe( - pxthToString(createPxth(['registeredUser', 'name'])) - ); + expect(getPxthSegments(defaultSetValue.mock.calls[0][0])).toStrictEqual(['registeredUser', 'name']); expect(defaultSetValue).toBeCalledWith(expect.anything(), 'Hello'); defaultSetValue.mockClear(); @@ -270,15 +266,13 @@ describe('Mapping proxy', () => { expect( defaultSetValue.mock.calls.findIndex( - ([path, value]) => - pxthToString(path) === pxthToString(createPxth(['registeredUser', 'name'])) && value === 'As' + ([path, value]) => samePxth(path, createPxth(['registeredUser', 'name'])) && value === 'As' ) !== -1 ).toBeTruthy(); expect( defaultSetValue.mock.calls.findIndex( - ([path, value]) => - pxthToString(path) === pxthToString(createPxth(['registeredUser', 'surname'])) && value === 'Df' + ([path, value]) => samePxth(path, createPxth(['registeredUser', 'surname'])) && value === 'Df' ) !== -1 ).toBeTruthy(); }); @@ -330,18 +324,21 @@ describe('Mapping proxy', () => { createPxth(['registeredUser']) ); - expect(pxthToString(proxy.getNormalPath(createPxth(['registeredUser', 'personalData'])))).toBe( - pxthToString(createPxth([])) - ); - expect(pxthToString(proxy.getNormalPath(createPxth(['registeredUser', 'registrationDate'])))).toBe( - pxthToString(createPxth(['registeredUser', 'dates', 'registration'])) - ); - expect(pxthToString(proxy.getNormalPath(createPxth(['registeredUser', 'personalData', 'name'])))).toBe( - pxthToString(createPxth(['registeredUser'])) - ); - expect(pxthToString(proxy.getNormalPath(createPxth(['registeredUser', 'location', 'city'])))).toBe( - pxthToString(createPxth(['registeredUser', 'personalData', 'home_location', 'city'])) - ); + expect(getPxthSegments(proxy.getNormalPath(createPxth(['registeredUser', 'personalData'])))).toStrictEqual([]); + expect(getPxthSegments(proxy.getNormalPath(createPxth(['registeredUser', 'registrationDate'])))).toStrictEqual([ + 'registeredUser', + 'dates', + 'registration', + ]); + expect( + getPxthSegments(proxy.getNormalPath(createPxth(['registeredUser', 'personalData', 'name']))) + ).toStrictEqual(['registeredUser']); + expect(getPxthSegments(proxy.getNormalPath(createPxth(['registeredUser', 'location', 'city'])))).toStrictEqual([ + 'registeredUser', + 'personalData', + 'home_location', + 'city', + ]); }); it('should return proxied path from normal path', () => { @@ -359,12 +356,15 @@ describe('Mapping proxy', () => { createPxth(['registeredUser']) ); - expect(pxthToString(proxy.getProxiedPath(createPxth(['registeredUser', 'dates', 'registration'])))).toBe( - pxthToString(createPxth(['registeredUser', 'registrationDate'])) - ); - expect(pxthToString(proxy.getProxiedPath(createPxth(['registeredUser', 'name'])))).toBe( - pxthToString(createPxth(['registeredUser', 'personalData', 'name', 'firstName'])) - ); + expect( + getPxthSegments(proxy.getProxiedPath(createPxth(['registeredUser', 'dates', 'registration']))) + ).toStrictEqual(['registeredUser', 'registrationDate']); + expect(getPxthSegments(proxy.getProxiedPath(createPxth(['registeredUser', 'name'])))).toStrictEqual([ + 'registeredUser', + 'personalData', + 'name', + 'firstName', + ]); expect(() => proxy.getProxiedPath(createPxth(['registeredUser', 'personalData']))).toThrow(); }); @@ -484,7 +484,7 @@ describe('Mapping proxy', () => { defaultWatch as (path: Pxth, observer: Observer) => () => void ); - expect(pxthToString(defaultWatch.mock.calls[0][0])).toBe('core.values.location_from.id'); + expect(getPxthSegments(defaultWatch.mock.calls[0][0])).toStrictEqual(['core', 'values', 'location_from', 'id']); expect(defaultWatch.mock.calls[0][1]).toBeDefined(); expect(observer).toBeCalledWith(42); }); diff --git a/test/utils/pathUtils.test.ts b/test/utils/pathUtils.test.ts deleted file mode 100644 index 546a8fc..0000000 --- a/test/utils/pathUtils.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { shuffle } from 'lodash'; -import { createPxth, getPxthSegments, pxthToString, RootPathToken } from 'pxth'; - -import { - isInnerPath, - joinPaths, - longestCommonPath, - normalizePath, - relativePath, - samePxth, -} from '../../src/utils/pathUtils'; - -describe('samePxth', () => { - it('should compare 2 paths', () => { - expect(samePxth(createPxth(['a', 'b']), createPxth(['a', 'b']))).toBeTruthy(); - expect(samePxth(createPxth(['a', 'b']), createPxth(['b', 'a']))).toBeFalsy(); - }); -}); - -describe('joinPaths', () => { - it('should join paths', () => { - expect(pxthToString(joinPaths(createPxth(['hello']), createPxth(['world'])))).toBe('hello.world'); - expect(pxthToString(joinPaths(createPxth([]), createPxth(['hello'])))).toBe('hello'); - expect(pxthToString(joinPaths(createPxth([]), createPxth(['hello']), createPxth(['world'])))).toBe( - 'hello.world' - ); - expect(getPxthSegments(joinPaths(createPxth(['hello.world', 'path']), createPxth(['another'])))).toStrictEqual([ - 'hello.world', - 'path', - 'another', - ]); - expect(getPxthSegments(joinPaths(createPxth(['..a.b.c...']), createPxth(['...'])))).toStrictEqual([ - '..a.b.c...', - '...', - ]); - }); -}); - -describe('normalizePath', () => { - it('" hello.tst[0].b " = "hello.tst.0.b"', () => { - expect(normalizePath(' hello.tst[0].b ')).toBe('hello.tst.0.b'); - }); -}); - -describe('isInnerPath', () => { - it('isInnerPath false', () => { - expect(isInnerPath('hello', 'b')).toBe(false); - expect(isInnerPath('hello', 'helloa')).toBe(false); - expect(isInnerPath('hello', 'chello')).toBe(false); - expect(isInnerPath('hello.asdf', 'hello.a')).toBe(false); - expect(isInnerPath('hello.asdf', 'helloa')).toBe(false); - }); - it('isInnerPath simple cases', () => { - expect(isInnerPath('hello', 'hello.asdf')).toBe(true); - expect(isInnerPath('hello', 'hello.asdf.asdf')).toBe(true); - expect(isInnerPath('hello', 'hello.hello.hello')).toBe(true); - }); - it('isInnerPath complex cases', () => { - expect(isInnerPath('hello.asdf.bsdf', 'hello.asdf.bsdf.lol.k.w')).toBe(true); - expect(isInnerPath('hello[0].bsdf', 'hello.0.bsdf.lol.k.w')).toBe(true); - }); -}); - -describe('longestCommonPath', () => { - it('hit cases', () => { - expect(longestCommonPath([])).toBe(RootPathToken); - expect(longestCommonPath([''])).toBe(''); - expect(longestCommonPath(['asdf'])).toBe('asdf'); - }); - it('should return longest common path', () => { - expect(longestCommonPath(['asdf', 'asdf.hello', 'asdf.bye', 'asdf.hello.bye'])).toBe('asdf'); - expect(longestCommonPath(['hello.this.is.world', 'hello.this.is.bye', 'hello.this.is'])).toBe('hello.this.is'); - }); - it('no common paths', () => { - expect(longestCommonPath(shuffle(['asdf', 'asdf.hello', 'asdf.bye', 'asdf.hello.bye', 'b']))).toBe( - RootPathToken - ); - expect( - longestCommonPath(shuffle(['hello.this.is.world', 'hello.this.is.bye', 'hello.this.is', 'ahello'])) - ).toBe(RootPathToken); - }); -}); - -describe('relativePath', () => { - it('hit cases', () => { - expect(() => relativePath(createPxth([' ']), createPxth(['hello', 'world', 'this']))).toThrow(); - expect(() => - relativePath( - createPxth(['hello', 'world', 'this', 'is', 'not', 'parent', 'path']), - createPxth(['hello', 'world', 'this', 'is', 'not', 'nested', 'path']) - ) - ).toThrow(); - expect( - pxthToString( - relativePath(createPxth(['hello', 'world', '0', 'same']), createPxth(['hello', 'world', '0', 'same'])) - ) - ).toBe(pxthToString(createPxth([]))); - expect(pxthToString(relativePath(createPxth([]), createPxth([])))).toBe(pxthToString(createPxth([]))); - expect(pxthToString(relativePath(createPxth([]), createPxth(['nested', 'path'])))).toBe( - pxthToString(createPxth(['nested', 'path'])) - ); - expect(() => relativePath(createPxth(['helo']), createPxth([]))).toThrow(); - expect(pxthToString(relativePath(createPxth(['', '', 'asdf']), createPxth(['', '', 'asdf', 'lol'])))).toBe( - pxthToString(createPxth(['lol'])) - ); - }); - it('simple cases', () => { - expect( - pxthToString(relativePath(createPxth(['hello', 'world']), createPxth(['hello', 'world', 'nested', 'path']))) - ).toBe(pxthToString(createPxth(['nested', 'path']))); - expect( - pxthToString( - relativePath( - createPxth(['yes', 'this', 'is', '0', 'some', 'path']), - createPxth(['yes', 'this', 'is', '0', 'some', 'path', 'asdf', 'lol']) - ) - ) - ).toBe(pxthToString(createPxth(['asdf', 'lol']))); - }); -}); diff --git a/test/utils/useInterceptors.test.ts b/test/utils/useInterceptors.test.ts index 71c8c0b..e018324 100644 --- a/test/utils/useInterceptors.test.ts +++ b/test/utils/useInterceptors.test.ts @@ -1,5 +1,5 @@ import { act, renderHook } from '@testing-library/react-hooks'; -import { createPxth, Pxth, pxthToString } from 'pxth'; +import { createPxth, getPxthSegments, Pxth } from 'pxth'; import { Stock, StockProxy, useStock } from '../../src'; import { intercept, useInterceptors } from '../../src/utils/useInterceptors'; @@ -64,7 +64,7 @@ describe('intercept', () => { const proxy = new DummyProxy(createPxth(['asdf'])); const standard = jest.fn(); const custom = jest.fn(); - intercept(proxy, createPxth(['asdf.hello.b']), standard, custom, []); + intercept(proxy, createPxth(['asdf', 'hello', 'b']), standard, custom, []); expect(standard).not.toBeCalled(); expect(custom).toBeCalled(); }); @@ -105,13 +105,13 @@ describe('proxy', () => { cleanup2(); }); - expect(pxthToString(watch.mock.calls[0][0])).toBe(pxthToString(createPxth(['dest']))); + expect(getPxthSegments(watch.mock.calls[0][0])).toStrictEqual(['dest']); expect(watch).toBeCalledWith(expect.anything(), observer, expect.any(Function)); expect(watch).toBeCalledTimes(1); - expect(pxthToString(setValue.mock.calls[0][0])).toBe(pxthToString(createPxth(['dest']))); + expect(getPxthSegments(setValue.mock.calls[0][0])).toStrictEqual(['dest']); expect(setValue).toBeCalledWith(expect.anything(), 'asdf', expect.any(Function)); expect(setValue).toBeCalledTimes(1); - expect(pxthToString(getValue.mock.calls[0][0])).toBe(pxthToString(createPxth(['dest']))); + expect(getPxthSegments(getValue.mock.calls[0][0])).toStrictEqual(['dest']); expect(getValue).toBeCalledTimes(1); }); @@ -143,10 +143,10 @@ describe('proxy', () => { values = result.current.getValues(); }); - expect(pxthToString(setValue.mock.calls[0][0])).toBe(pxthToString(createPxth(['dest']))); + expect(getPxthSegments(setValue.mock.calls[0][0])).toStrictEqual(['dest']); expect(setValue).toBeCalledTimes(1); - expect(pxthToString(getValue.mock.calls[0][0])).toBe(pxthToString(createPxth(['dest']))); + expect(getPxthSegments(getValue.mock.calls[0][0])).toStrictEqual(['dest']); expect(getValue).toBeCalledTimes(1); expect(values).toStrictEqual({