Created
July 12, 2021 11:26
-
-
Save markwhitfeld/f0d4592bda7592e35b4a3e3d478cb69a to your computer and use it in GitHub Desktop.
Gist backup of mailok/todo-ngxs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | |
]); | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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