Skip to content

Commit

Permalink
feat: Add typeArray and allow passing equal function to bindableDerived
Browse files Browse the repository at this point in the history
  • Loading branch information
Marko OLEKSIYENKO committed Oct 4, 2023
1 parent 55773bc commit 6be18f9
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 17 deletions.
21 changes: 20 additions & 1 deletion core/lib/services/checks.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {describe, expect, test} from 'vitest';
import {isBoolean, isFunction, isNumber, clamp, isString} from './checks';
import {isBoolean, isFunction, isNumber, clamp, isString, isArray} from './checks';

describe('Checks', () => {
test(`'isNumber' should check if value is a number`, () => {
Expand Down Expand Up @@ -79,6 +79,25 @@ describe('Checks', () => {
expect(isString(() => {})).toBe(false);
});

test(`'isArray' should check if the value is an array`, () => {
expect(isArray(true)).toBe(false);
expect(isArray(false)).toBe(false);
expect(isArray(0)).toBe(false);
expect(isArray(1)).toBe(false);
expect(isArray(1.1)).toBe(false);
expect(isArray('1')).toBe(false);
expect(isArray('0')).toBe(false);
expect(isArray('1.1')).toBe(false);
expect(isArray(undefined)).toBe(false);
expect(isArray(null)).toBe(false);
expect(isArray({})).toBe(false);
expect(isArray([])).toBe(true);
expect(isArray(NaN)).toBe(false);
expect(isArray(Infinity)).toBe(false);
expect(isArray(-Infinity)).toBe(false);
expect(isArray(() => {})).toBe(false);
});

test(`'getValueInRange' should return a value is within a specific range`, () => {
expect(clamp(1, 5)).toBe(1);
expect(clamp(-1, 5)).toBe(0);
Expand Down
9 changes: 9 additions & 0 deletions core/lib/services/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ export function isString(value: any): value is string {
return typeof value === 'string';
}

/**
* an array type guard
* @param value the value to check
* @returns true if the value is an array
*/
export function isArray(value: any) {
return Array.isArray(value);
}

// TODO should we check that max > min?
/**
* Clamp the value based on a maximum and optional minimum
Expand Down
34 changes: 33 additions & 1 deletion core/lib/services/stores.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,9 @@ describe(`Stores service`, () => {
const valueMax$ = writable(2);

const value$ = bindableDerived(onValueChange$, [dirtyValue$, valueMax$], ([dirtyValue, valueMax]) => Math.min(dirtyValue, valueMax));
const unsubscribe = value$.subscribe((value) => values.push(value));
const unsubscribe = value$.subscribe((value) => {
values.push(value);
});
expect(values).toEqual([1]);
valueMax$.set(3); // no change
expect(onChangeCalls).toEqual([]);
Expand Down Expand Up @@ -442,5 +444,35 @@ describe(`Stores service`, () => {
expect(values).toEqual([1, 2]);
unsubscribe();
});

test(`should override equals function`, () => {
const onChangeCalls: number[][] = [];
const values: number[][] = [];
const dirtyValue$ = writable([1]);
const onValueChange$ = writable((value: number[]) => {
onChangeCalls.push(value);
});

const value$ = bindableDerived(
onValueChange$,
[dirtyValue$],
([dirtyValue]) => dirtyValue.map((dv) => Math.floor(dv)),
(a, b) => a.every((val, index) => val === b[index])
);
value$.subscribe((value) => values.push(value));
expect(values).toEqual([[1]]);

dirtyValue$.set([1]); // no change
expect(onChangeCalls).toEqual([]);
expect(values).toEqual([[1]]);

dirtyValue$.set([2.5]);
expect(onChangeCalls).toEqual([[2]]);
expect(values).toEqual([[1], [2]]);

dirtyValue$.set([5.6, 7.8]);
expect(onChangeCalls).toEqual([[2], [5, 7]]);
expect(values).toEqual([[1], [2], [5, 7]]);
});
});
});
32 changes: 18 additions & 14 deletions core/lib/services/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,21 +321,25 @@ export const stateStores = <A extends {[key in `${string}$`]: ReadableSignal<any
export const bindableDerived = <T, U extends [WritableSignal<T>, ...StoreInput<any>[]]>(
onChange$: ReadableSignal<(value: T) => void>,
stores: U,
adjustValue: (arg: StoresInputValues<U>) => T
adjustValue: (arg: StoresInputValues<U>) => T,
equal = (currentValue: T, newValue: T) => newValue === currentValue
) => {
let currentValue = stores[0]();
return derived(stores, (values) => {
const newValue = adjustValue(values);
const rectifiedValue = newValue !== values[0];
if (rectifiedValue) {
stores[0].set(newValue);
}
if (rectifiedValue || newValue !== currentValue) {
currentValue = newValue;
// TODO check if we should do this async to avoid issue
// with angular and react only when rectifiedValue is true?
onChange$()(newValue);
}
return newValue;
return derived(stores, {
derive(values) {
const newValue = adjustValue(values);
const rectifiedValue = !equal(values[0], newValue);
if (rectifiedValue) {
stores[0].set(newValue);
}
if (rectifiedValue || !equal(currentValue, newValue)) {
currentValue = newValue;
// TODO check if we should do this async to avoid issue
// with angular and react only when rectifiedValue is true?
onChange$()(newValue);
}
return newValue;
},
equal,
});
};
13 changes: 12 additions & 1 deletion core/lib/services/writables.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {isBoolean, isFunction, isNumber, isString} from './checks';
import {isArray, isBoolean, isFunction, isNumber, isString} from './checks';
import type {WritableWithDefaultOptions} from './stores';
import {INVALID_VALUE} from './stores';

Expand All @@ -23,3 +23,14 @@ export const typeFunction: WritableWithDefaultOptions<(...args: any[]) => any> =
normalizeValue: testToNormalizeValue(isFunction),
equal: Object.is,
};

export const typeArray: WritableWithDefaultOptions<any[]> = {
normalizeValue: testToNormalizeValue(isArray),
equal: (a, b) => {
if (a === b) {
return true;

Check warning on line 31 in core/lib/services/writables.ts

View check run for this annotation

Codecov / codecov/patch

core/lib/services/writables.ts#L31

Added line #L31 was not covered by tests
} else {
return a.every((val, index) => val === b[index]);

Check warning on line 33 in core/lib/services/writables.ts

View check run for this annotation

Codecov / codecov/patch

core/lib/services/writables.ts#L33

Added line #L33 was not covered by tests
}
},
};

0 comments on commit 6be18f9

Please sign in to comment.