mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
docs(plan): store bindings (#283)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export { uniqBy } from './uniq-by';
|
||||
@@ -0,0 +1,59 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { uniqBy } from '../uniq-by';
|
||||
|
||||
describe('uniqBy', () => {
|
||||
it('removes duplicates keeping last occurrence', () => {
|
||||
const arr = [
|
||||
{ id: 'a', v: 1 },
|
||||
{ id: 'b', v: 2 },
|
||||
{ id: 'a', v: 3 },
|
||||
];
|
||||
|
||||
const result = uniqBy(arr, item => item.id);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 'b', v: 2 },
|
||||
{ id: 'a', v: 3 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns empty array for empty input', () => {
|
||||
const result = uniqBy([], item => item);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns same array when no duplicates', () => {
|
||||
const arr = [{ id: 'a' }, { id: 'b' }, { id: 'c' }];
|
||||
|
||||
const result = uniqBy(arr, item => item.id);
|
||||
|
||||
expect(result).toEqual(arr);
|
||||
});
|
||||
|
||||
it('works with primitive values', () => {
|
||||
const arr = [1, 2, 3, 2, 1];
|
||||
|
||||
const result = uniqBy(arr, item => item);
|
||||
|
||||
expect(result).toEqual([3, 2, 1]);
|
||||
});
|
||||
|
||||
it('preserves order with last occurrence winning', () => {
|
||||
const arr = [
|
||||
{ id: 'x', order: 1 },
|
||||
{ id: 'y', order: 2 },
|
||||
{ id: 'z', order: 3 },
|
||||
{ id: 'x', order: 4 },
|
||||
{ id: 'y', order: 5 },
|
||||
];
|
||||
|
||||
const result = uniqBy(arr, item => item.id);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 'z', order: 3 },
|
||||
{ id: 'x', order: 4 },
|
||||
{ id: 'y', order: 5 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Returns array with duplicates removed, keeping the LAST occurrence.
|
||||
* Useful for slice merging where extensions should override base slices.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const slices = [{ id: 'a', v: 1 }, { id: 'b', v: 2 }, { id: 'a', v: 3 }];
|
||||
* uniqBy(slices, s => s.id);
|
||||
* // => [{ id: 'b', v: 2 }, { id: 'a', v: 3 }]
|
||||
* ```
|
||||
*/
|
||||
export function uniqBy<T, K>(arr: T[], mapper: (item: T) => K): T[] {
|
||||
const seen = new Map<K, number>();
|
||||
arr.forEach((item, i) => seen.set(mapper(item), i));
|
||||
return arr.filter((_, i) => [...seen.values()].includes(i));
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { isNil } from '../predicate';
|
||||
|
||||
/**
|
||||
* Composes multiple callbacks into one. All callbacks receive same args, no return value.
|
||||
* Returns undefined if no callbacks provided.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const onSetup = composeCallbacks(base.onSetup, extension.onSetup);
|
||||
* onSetup?.(ctx); // Calls both if defined
|
||||
* ```
|
||||
*/
|
||||
export function composeCallbacks<T extends (...args: any[]) => void>(...fns: (T | undefined | null)[]): T | undefined {
|
||||
const defined = fns.filter((fn): fn is T => !isNil(fn));
|
||||
|
||||
if (defined.length === 0) return undefined;
|
||||
|
||||
if (defined.length === 1) return defined[0];
|
||||
|
||||
return ((...args: Parameters<T>) => {
|
||||
defined.forEach(fn => fn(...args));
|
||||
}) as T;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { composeCallbacks } from './compose-callbacks';
|
||||
@@ -0,0 +1,79 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { composeCallbacks } from '../compose-callbacks';
|
||||
|
||||
describe('composeCallbacks', () => {
|
||||
it('returns undefined when no callbacks provided', () => {
|
||||
const result = composeCallbacks();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined when all callbacks are null/undefined', () => {
|
||||
const result = composeCallbacks(null, undefined, null);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns single callback when only one provided', () => {
|
||||
const fn = vi.fn();
|
||||
const result = composeCallbacks(fn);
|
||||
|
||||
expect(result).toBe(fn);
|
||||
});
|
||||
|
||||
it('returns single callback when others are null/undefined', () => {
|
||||
const fn = vi.fn();
|
||||
const result = composeCallbacks(null, fn, undefined);
|
||||
|
||||
expect(result).toBe(fn);
|
||||
});
|
||||
|
||||
it('calls all callbacks with same args', () => {
|
||||
const fn1 = vi.fn();
|
||||
const fn2 = vi.fn();
|
||||
const fn3 = vi.fn();
|
||||
|
||||
const composed = composeCallbacks(fn1, fn2, fn3);
|
||||
composed?.('arg1', 'arg2');
|
||||
|
||||
expect(fn1).toHaveBeenCalledWith('arg1', 'arg2');
|
||||
expect(fn2).toHaveBeenCalledWith('arg1', 'arg2');
|
||||
expect(fn3).toHaveBeenCalledWith('arg1', 'arg2');
|
||||
});
|
||||
|
||||
it('calls callbacks in order', () => {
|
||||
const order: number[] = [];
|
||||
const fn1 = () => order.push(1);
|
||||
const fn2 = () => order.push(2);
|
||||
const fn3 = () => order.push(3);
|
||||
|
||||
const composed = composeCallbacks(fn1, fn2, fn3);
|
||||
composed?.();
|
||||
|
||||
expect(order).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('skips null/undefined in the middle', () => {
|
||||
const fn1 = vi.fn();
|
||||
const fn2 = vi.fn();
|
||||
|
||||
const composed = composeCallbacks(fn1, null, undefined, fn2);
|
||||
composed?.();
|
||||
|
||||
expect(fn1).toHaveBeenCalled();
|
||||
expect(fn2).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('works with typed callbacks', () => {
|
||||
type OnSetup = (ctx: { name: string }) => void;
|
||||
|
||||
const fn1: OnSetup = vi.fn();
|
||||
const fn2: OnSetup = vi.fn();
|
||||
|
||||
const composed = composeCallbacks<OnSetup>(fn1, fn2);
|
||||
const ctx = { name: 'test' };
|
||||
composed?.(ctx);
|
||||
|
||||
expect(fn1).toHaveBeenCalledWith(ctx);
|
||||
expect(fn2).toHaveBeenCalledWith(ctx);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user