Skip to content

Instantly share code, notes, and snippets.

@markwhitfeld
Created July 12, 2021 11:26
Show Gist options
  • Save markwhitfeld/f0d4592bda7592e35b4a3e3d478cb69a to your computer and use it in GitHub Desktop.
Save markwhitfeld/f0d4592bda7592e35b4a3e3d478cb69a to your computer and use it in GitHub Desktop.
Gist backup of mailok/todo-ngxs
import { Predicate } from '@ngxs/store/operators/internals';
import { isPredicate, findIndices, isArrayNumber, invalidIndexs } from './utils';
/**
* @param selector - indices or predicate to remove multiple items from an array by
*/
export function removeManyItems<T>(selector: number[] | Predicate<T>) {
return function removeItemsOperator(existing: Readonly<T[]>): T[] {
let indices = [];
if (isPredicate(selector)) {
indices = findIndices(selector, existing);
} else if (isArrayNumber(selector)) {
indices = selector;
}
if (invalidIndexs(indices, existing)) {
return existing;
}
return existing.filter((_, index) => (indices.findIndex(i => i === index) >= 0 ? false : true));
};
}
import { updateManyItems } from './update-many-items';
import { FilterType } from '../../utils';
import { patch } from '@ngxs/store/operators';
import { Todo } from '../../models';
import { TodoStateModel } from '../todo.state';
describe('update items', () => {
let todoStateNull: (todos?: Todo[], filter?: string) => TodoStateModel;
let todoStateUndefined: (todos?: Todo[], filter?: string) => TodoStateModel;
let stateTodoArrayString;
let stateTodoArrayNumbers;
let stateTodoArrayBooleans;
let todoState: (todos?: Todo[], filter?: string) => TodoStateModel;
let todosArray: Todo[];
let activeTodos: Todo[];
let completeTodos: Todo[];
beforeEach(() => {
todoStateNull = (todos?: Todo[], filter?: string): TodoStateModel => ({
todos: null,
filter: filter ? filter : FilterType.SHOW_ALL
});
todoStateUndefined = (todos?: Todo[], filter?: string): TodoStateModel => ({
todos: undefined,
filter: filter ? filter : FilterType.SHOW_ALL
});
stateTodoArrayString = {
todos: ['A', 'B', 'C', 'D']
};
stateTodoArrayNumbers = {
todos: [1, 2, 3, 4]
};
stateTodoArrayBooleans = {
todos: [true, true, true, false]
};
todoState = (todos?: Todo[], filter?: string): TodoStateModel => ({
todos: todos ? todos : todosArray,
filter: filter ? filter : FilterType.SHOW_ALL
});
completeTodos = [{ id: 2, text: 'B', completed: true }, { id: 3, text: 'C', completed: true }];
activeTodos = [{ id: 1, text: 'A', completed: false }];
todosArray = [...activeTodos, ...completeTodos];
});
describe('when null provided', () => {
test('should return the same root if selector is null', () => {
const original = todoState();
const newValue = patch({
todos: updateManyItems(null, {})
})(original);
expect(newValue).toBe(original);
});
test('should return the same root if operatorOrValue is null', () => {
const original = todoState();
const newValue = patch({
todos: updateManyItems(() => true, null)
})(original);
expect(newValue).toBe(original);
});
test('should return the same root if source is null', () => {
const original = todoStateNull();
const newValue = patch({
todos: updateManyItems(() => true, {})
})(original);
expect(newValue).toBe(original);
});
});
describe('when undefined provided', () => {
test('should return the same root if selector is undefined', () => {
const original = todoState();
const newValue = patch({
todos: updateManyItems(undefined, {})
})(original);
expect(newValue).toBe(original);
});
test('should return the same root if operatorOrValue is undefined', () => {
const original = todoState();
const newValue = patch({
todos: updateManyItems(() => true, undefined)
})(original);
expect(newValue).toBe(original);
});
test('should return the same root if source is undefined', () => {
const original = todoStateUndefined();
const newValue = patch<any>({
a: updateManyItems(() => true, {})
})(original);
expect(newValue).toBe(original);
});
});
describe('when exist any index that are invalid', () => {
test('should return the same root if exist any indices invalid', () => {
const original = stateTodoArrayBooleans;
const newValue: {
todos: boolean[];
} = patch({
todos: updateManyItems<boolean>([0, 50], false)
})(original);
expect(newValue.todos).toEqual(original.todos);
});
});
describe('when source is a primitive array', () => {
test('should return a new root with changed items if operatorOrValue provided is a value', () => {
const newValue: {
todos: string[];
} = patch({
todos: updateManyItems<string>(item => item === 'C', 'H')
})(stateTodoArrayString);
expect(newValue.todos).toEqual(['A', 'B', 'H', 'D']);
});
});
describe('when source is a object array', () => {
test('should return a new root with the items changed', () => {
const original = todoState();
const newValue = patch<TodoStateModel>({
todos: updateManyItems<Todo>(item => item.completed === true, patch<Todo>({ text: 'completed!' }))
})(original);
expect(newValue.todos).toEqual([
{ id: 1, text: 'A', completed: false },
{ id: 2, text: 'completed!', completed: true },
{ id: 3, text: 'completed!', completed: true }
]);
});
test('should return a new root with the items changed if operatorOrValue provide is a partial', () => {
const original = todoState();
const newValue = patch<TodoStateModel>({
todos: updateManyItems<Todo>(item => item.completed === true, { text: 'completed!' })
})(original);
expect(newValue.todos).toEqual([
{ id: 1, text: 'A', completed: false },
{ id: 2, text: 'completed!', completed: true },
{ id: 3, text: 'completed!', completed: true }
]);
});
});
});
import { StateOperator } from '@ngxs/store';
import { Predicate } from '@ngxs/store/operators/internals';
import { findIndices, invalidIndexs, isArrayNumber, isObject, isPredicate, isStateOperator } from './utils';
/**
* @param selector - Array of indices or a predicate function
* that can be provided in `Array.prototype.findIndex`
* @param operatorOrValue - New value under the `selector` index or a
* function that can be applied to an existing value
*/
export function updateManyItems<T>(
selector: number[] | Predicate<T>,
operatorOrValue: Partial<T> | StateOperator<T>
): StateOperator<T[]> {
return function updateItemsOperator(existing: Readonly<T[]>) {
let indices = [];
if (!selector || !operatorOrValue) {
return existing;
}
if (!existing) {
return existing;
}
if (isPredicate(selector)) {
indices = findIndices(selector, existing);
} else if (isArrayNumber(selector)) {
indices = selector;
}
if (invalidIndexs(indices, existing)) {
return existing;
}
let values: Record<number, T> = {};
if (isStateOperator(operatorOrValue)) {
values = indices.reduce((acc, it) => ({ ...acc, [it]: operatorOrValue(existing[it]) }), {});
} else {
values = indices.reduce(
(acc, it) =>
isObject(existing[it])
? { ...acc, [it]: { ...existing[it], ...operatorOrValue } }
: { ...acc, [it]: operatorOrValue },
{}
);
}
const clone = [...existing];
const keys = Object.keys(values);
for (const i in keys) {
clone[keys[i]] = values[keys[i]];
}
return clone;
};
}
import { Predicate } from '@ngxs/store/operators/internals';
import { StateOperator } from '@ngxs/store';
export function isPredicate<T>(value: Predicate<T> | boolean | number | number[]): value is Predicate<T> {
return typeof value === 'function';
}
export function findIndices<T>(predicate: Predicate<T>, existing: Readonly<T[]>): number[] {
return existing.reduce((acc, it, i) => {
const index = predicate(it) ? i : -1;
return invalidIndex(index) ? acc : [...acc, index];
}, []);
}
export function isArrayNumber(value: number[]): boolean {
for (const i in value) {
if (!isNumber(value[i])) {
return false;
}
}
return true;
}
export function invalidIndexs<T>(indices: number[], existing: Readonly<T[]>): boolean {
for (const i in indices) {
if (!existing[indices[i]] || !isNumber(indices[i]) || invalidIndex(indices[i])) {
return true;
}
}
return false;
}
export function isStateOperator<T>(value: Partial<T> | StateOperator<T>): value is StateOperator<T> {
return typeof value === 'function';
}
export function isNumber(value: any): value is number {
return typeof value === 'number';
}
export function invalidIndex(index: number): boolean {
return Number.isNaN(index) || index === -1;
}
export function isObject(value: any): boolean {
return typeof value === 'object';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment