diff --git a/theatre/shared/src/utils/mutableSetDeep.ts b/theatre/shared/src/utils/mutableSetDeep.ts index d8b0f94..ab8cc0e 100644 --- a/theatre/shared/src/utils/mutableSetDeep.ts +++ b/theatre/shared/src/utils/mutableSetDeep.ts @@ -2,11 +2,24 @@ import type {Pointer} from '@theatre/dataverse' import {getPointerParts, pointer} from '@theatre/dataverse' import lodashSet from 'lodash-es/set' +/** + * Like `lodash.set`, but type-safe, as it uses `Pointer` instead of string/array. + * + * `getPointer` is a function that takes a pointer to `obj`, and returns a pointer + * to the path that you want to set. This API looks funny but it is actually convenient + * to mutate values type-safe, as you can see in the example below: + * + * ```ts + * mutableSetDeep({a: {b: 1}}, (p) => p.a.b, 2) // {a: {b: 2}} + * ``` + */ export default function mutableSetDeep( obj: O, - getPath: (p: Pointer) => Pointer, + getPointer: (p: Pointer) => Pointer, val: T, ) { - const path = getPointerParts(getPath(pointer({root: {}, path: []}))).path - lodashSet(obj, path, val) + const rootPointer = pointer({root: {}, path: []}) + const deepPointer = getPointer(rootPointer) + + lodashSet(obj, getPointerParts(deepPointer).path, val) } diff --git a/theatre/shared/src/utils/noop.ts b/theatre/shared/src/utils/noop.ts index 9b98d9c..e6ba6e9 100644 --- a/theatre/shared/src/utils/noop.ts +++ b/theatre/shared/src/utils/noop.ts @@ -1,3 +1,6 @@ +/** + * Just an empty function + */ const noop = () => {} export default noop diff --git a/theatre/shared/src/utils/numberRoundingUtils.test.ts b/theatre/shared/src/utils/numberRoundingUtils.test.ts index 2ca41a2..316dac5 100644 --- a/theatre/shared/src/utils/numberRoundingUtils.test.ts +++ b/theatre/shared/src/utils/numberRoundingUtils.test.ts @@ -1,4 +1,3 @@ -import type {IUtilContext} from '@theatre/shared/logger' import { getLastMultipleOf, numberOfDecimals, @@ -28,44 +27,36 @@ const example = ( ) } -const CTX: IUtilContext = { - get logger(): never { - throw new Error('unexpected logger access in test example') - }, -} - describe(`numberRoundingUtils()`, () => { describe(`roundestNumberBetween()`, () => { - example(roundestNumberBetween, [CTX, 0.1, 1.1], 1) - example(roundestNumberBetween, [CTX, 0.1111111123, 0.2943439448], 0.25) - example(roundestNumberBetween, [CTX, 0.19, 0.23], 0.2) - example(roundestNumberBetween, [CTX, -0.19, 0.23], 0) - example(roundestNumberBetween, [CTX, -0.19, -0.02], -0.1, {debug: false}) - example(roundestNumberBetween, [CTX, -0.19, -0.022], -0.1, {debug: false}) - example(roundestNumberBetween, [CTX, -0.19, -0.022234324], -0.1, { - debug: false, - }) - example(roundestNumberBetween, [CTX, -0.19, 0.0222222], 0) - example(roundestNumberBetween, [CTX, -0.19, 0.02], 0) + example(roundestNumberBetween, [0.1, 1.1], 1) + example(roundestNumberBetween, [0.1111111123, 0.2943439448], 0.25) + example(roundestNumberBetween, [0.19, 0.23], 0.2) + example(roundestNumberBetween, [-0.19, 0.23], 0) + example(roundestNumberBetween, [-0.19, -0.02], -0.1, {debug: false}) + example(roundestNumberBetween, [-0.19, -0.022], -0.1, {debug: false}) + example(roundestNumberBetween, [-0.19, -0.022234324], -0.1, {debug: false}) + example(roundestNumberBetween, [-0.19, 0.0222222], 0) + example(roundestNumberBetween, [-0.19, 0.02], 0) example( roundestNumberBetween, - [CTX, 22304.2398427391, 22304.2398427393], + [22304.2398427391, 22304.2398427393], 22304.2398427392, ) - example(roundestNumberBetween, [CTX, 22304.2398427391, 22304.4], 22304.25) - example(roundestNumberBetween, [CTX, 902, 901], 902) - example(roundestNumberBetween, [CTX, -10, -5], -10) - example(roundestNumberBetween, [CTX, -5, -10], -10) - example(roundestNumberBetween, [CTX, -10, -5], -10) + example(roundestNumberBetween, [22304.2398427391, 22304.4], 22304.25) + example(roundestNumberBetween, [902, 901], 902) + example(roundestNumberBetween, [-10, -5], -10) + example(roundestNumberBetween, [-5, -10], -10) + example(roundestNumberBetween, [-10, -5], -10) example( roundestNumberBetween, - [CTX, -0.00876370109231405, -2.909374013346118e-50], + [-0.00876370109231405, -2.909374013346118e-50], 0, {debug: false}, ) example( roundestNumberBetween, - [CTX, 0.059449443526800295, 0.06682093143783596], + [0.059449443526800295, 0.06682093143783596], 0.06, {debug: false}, ) @@ -82,7 +73,7 @@ describe(`numberRoundingUtils()`, () => { const from = toPrecision(getRandomNumber()) const to = toPrecision(getRandomNumber()) - const result = roundestNumberBetween(CTX, from, to) + const result = roundestNumberBetween(from, to) if (from < to) { if (result < from || result > to) { throw new Error(`Invalid: ${from} ${to} ${result}`) @@ -96,43 +87,43 @@ describe(`numberRoundingUtils()`, () => { }) }) describe(`roundestIntegerBetween`, () => { - example(roundestIntegerBetween, [CTX, -1, 6], 0, {}) - example(roundestIntegerBetween, [CTX, 0, 6], 0, {}) - example(roundestIntegerBetween, [CTX, -1, 0], 0, {}) - example(roundestIntegerBetween, [CTX, -1850, -1740], -1750, {}) - example(roundestIntegerBetween, [CTX, 1, 6], 5, {}) - example(roundestIntegerBetween, [CTX, 1, 5], 5) - example(roundestIntegerBetween, [CTX, 1, 2], 2) - example(roundestIntegerBetween, [CTX, 1, 10], 10) - example(roundestIntegerBetween, [CTX, 1, 12], 10) - example(roundestIntegerBetween, [CTX, 11, 15], 15) - example(roundestIntegerBetween, [CTX, 101, 102], 102, {debug: true}) - example(roundestIntegerBetween, [CTX, 11, 14, false], 12) - example(roundestIntegerBetween, [CTX, 11, 14, true], 12.5) - example(roundestIntegerBetween, [CTX, 11, 12], 12) - example(roundestIntegerBetween, [CTX, 11, 12], 12, {}) - example(roundestIntegerBetween, [CTX, 10, 90], 50) - example(roundestIntegerBetween, [CTX, 10, 100], 100) - example(roundestIntegerBetween, [CTX, 10, 110], 100) - example(roundestIntegerBetween, [CTX, 9, 100], 10) - example(roundestIntegerBetween, [CTX, 9, 1100], 10) - example(roundestIntegerBetween, [CTX, 9, 699], 10) - example(roundestIntegerBetween, [CTX, 9, 400], 10) - example(roundestIntegerBetween, [CTX, 9, 199], 10) - example(roundestIntegerBetween, [CTX, 9, 1199], 10) - example(roundestIntegerBetween, [CTX, 1921, 1998], 1950) - example(roundestIntegerBetween, [CTX, 1921, 2020], 2000) - example(roundestIntegerBetween, [CTX, 1601, 1998], 1750) - example(roundestIntegerBetween, [CTX, 1919, 1921], 1920) - example(roundestIntegerBetween, [CTX, 1919, 1919], 1919) - example(roundestIntegerBetween, [CTX, 3901, 3902], 3902) - example(roundestIntegerBetween, [CTX, 901, 902], 902) + example(roundestIntegerBetween, [-1, 6], 0, {}) + example(roundestIntegerBetween, [0, 6], 0, {}) + example(roundestIntegerBetween, [-1, 0], 0, {}) + example(roundestIntegerBetween, [-1850, -1740], -1750, {}) + example(roundestIntegerBetween, [1, 6], 5, {}) + example(roundestIntegerBetween, [1, 5], 5) + example(roundestIntegerBetween, [1, 2], 2) + example(roundestIntegerBetween, [1, 10], 10) + example(roundestIntegerBetween, [1, 12], 10) + example(roundestIntegerBetween, [11, 15], 15) + example(roundestIntegerBetween, [101, 102], 102, {debug: true}) + example(roundestIntegerBetween, [11, 14, false], 12) + example(roundestIntegerBetween, [11, 14, true], 12.5) + example(roundestIntegerBetween, [11, 12], 12) + example(roundestIntegerBetween, [11, 12], 12, {}) + example(roundestIntegerBetween, [10, 90], 50) + example(roundestIntegerBetween, [10, 100], 100) + example(roundestIntegerBetween, [10, 110], 100) + example(roundestIntegerBetween, [9, 100], 10) + example(roundestIntegerBetween, [9, 1100], 10) + example(roundestIntegerBetween, [9, 699], 10) + example(roundestIntegerBetween, [9, 400], 10) + example(roundestIntegerBetween, [9, 199], 10) + example(roundestIntegerBetween, [9, 1199], 10) + example(roundestIntegerBetween, [1921, 1998], 1950) + example(roundestIntegerBetween, [1921, 2020], 2000) + example(roundestIntegerBetween, [1601, 1998], 1750) + example(roundestIntegerBetween, [1919, 1921], 1920) + example(roundestIntegerBetween, [1919, 1919], 1919) + example(roundestIntegerBetween, [3901, 3902], 3902) + example(roundestIntegerBetween, [901, 902], 902) }) describe(`roundestFloat()`, () => { - example(roundestFloat, [CTX, 0.19, 0.2122], 0.2) - example(roundestFloat, [CTX, 0.19, 0.31], 0.25) - example(roundestFloat, [CTX, 0.19, 0.41], 0.25) - example(roundestFloat, [CTX, 0.19, 1.9], 0.5) + example(roundestFloat, [0.19, 0.2122], 0.2) + example(roundestFloat, [0.19, 0.31], 0.25) + example(roundestFloat, [0.19, 0.41], 0.25) + example(roundestFloat, [0.19, 1.9], 0.5) }) describe(`numberOfDecimals()`, () => { example(numberOfDecimals, [1.1], 1) diff --git a/theatre/shared/src/utils/numberRoundingUtils.ts b/theatre/shared/src/utils/numberRoundingUtils.ts index 2a0784d..3c5f978 100644 --- a/theatre/shared/src/utils/numberRoundingUtils.ts +++ b/theatre/shared/src/utils/numberRoundingUtils.ts @@ -1,29 +1,39 @@ import padEnd from 'lodash-es/padEnd' -import type {IUtilContext} from '@theatre/shared/logger' +import logger from '@theatre/shared/logger' -export function roundestNumberBetween( - ctx: IUtilContext, - _a: number, - _b: number, -): number { +/** + * Returns the _roundest_ number `c`, such that `a <= c <= b`. This is useful + * when a number value is beinged "nudged" by a user, and we want to avoid setting + * it to weird value like `101.1239293814314`, when we know that the user probably just meant `100`. + * + * Examples + * ```ts + * roundestNumberBetween(0.1111111123, 0.2943439448) // 0.25 + * roundestNumberBetween(0.19, 0.23) // 0.2 + * roundestNumberBetween(1921, 1998) // 1950 + * roundestNumberBetween(10, 110) // 100 + * // There are many more examples at `./numberRoundingUtils.test.ts` + * ``` + */ +export function roundestNumberBetween(_a: number, _b: number): number { if (_b < _a) { - return roundestNumberBetween(ctx, _b, _a) + return roundestNumberBetween(_b, _a) } if (_a < 0 && _b < 0) { - return noMinusZero(roundestNumberBetween(ctx, -_b, -_a) * -1) + return noMinusZero(roundestNumberBetween(-_b, -_a) * -1) } if (_a <= 0 && _b >= 0) return 0 const aCeiling = Math.ceil(_a) if (aCeiling <= _b) { - return roundestIntegerBetween(ctx, aCeiling, Math.floor(_b)) + return roundestIntegerBetween(aCeiling, Math.floor(_b)) } else { const [a, b] = [_a, _b] const integer = Math.floor(a) - return integer + roundestFloat(ctx, a - integer, b - integer) + return integer + roundestFloat(a - integer, b - integer) } } @@ -32,7 +42,6 @@ const halvesAndQuartiles = [5, 2.5, 7.5] const multipliersWithoutQuartiles = [5, 2, 4, 6, 8, 1, 3, 7, 9] export function roundestIntegerBetween( - ctx: IUtilContext, _a: number, _b: number, decimalsAllowed: boolean = true, @@ -82,9 +91,7 @@ export function roundestIntegerBetween( base = highestTotalFound if (currentExponentiationOfTen === 1) { - ctx.logger.error( - `Coudn't find a human-readable number between ${a} and ${b}`, - ) + logger.error(`Coudn't find a human-readable number between ${a} and ${b}`) return _a } else { currentExponentiationOfTen /= 10 @@ -133,11 +140,7 @@ export const stringifyNumber = (n: number): string => { /** * it is expected that both args are 0 \< arg \< 1 */ -export const roundestFloat = ( - ctx: IUtilContext, - a: number, - b: number, -): number => { +export const roundestFloat = (a: number, b: number): number => { const inString = { a: stringifyNumber(a), b: stringifyNumber(b), @@ -171,7 +174,6 @@ export const roundestFloat = ( } const roundestInt = roundestIntegerBetween( - ctx, parseInt(withPaddedDecimals.a, 10) * Math.pow(10, maxNumberOfLeadingZeros), parseInt(withPaddedDecimals.b, 10) * Math.pow(10, maxNumberOfLeadingZeros), true,