feat(ui): add changeNamedCollection util

pull/5937/head
Pavel Zavora 2022-06-10 18:41:31 +02:00
parent ea1a9fe545
commit a51ce4d8a1
2 changed files with 132 additions and 0 deletions

View File

@ -0,0 +1,64 @@
interface Named {
name: string
}
type NamedChanges = Record<string, boolean>
/**
* ComputeNamedChanges computes changes between array of named instances
* as a Record with instance name as a key and values indicated addition (true)
* or removal (false). Undefined is returned if there are no changes detected.
*/
export function computeNamedChanges<T extends Named>(
prev: T[],
next: T[]
): NamedChanges | undefined {
const retVal = {}
// indicate all prev values as removed
prev.forEach(({name}) => (retVal[name] = false))
// indicate all new values as added, existing as undefined
next.forEach(
({name}) => (retVal[name] = retVal[name] === false ? undefined : true)
)
// filter all undefined values
let changed = false
const onlyChanges = Object.entries(retVal).reduce((acc, [key, value]) => {
if (value !== undefined) {
changed = true
acc[key] = value
}
return acc
}, {})
return changed ? onlyChanges : undefined
}
/**
* ChangeNamedCollection returns a collection that changes the
* supplied named collection according to changeType. True changeType
* inserts an element in a sorted way, false removes all elements
* of the same name, undefined does nothing and returns the
* supplied collection.
*/
export function changeNamedCollection<T extends Named>(
values: T[],
element: T,
changeType: boolean | undefined
): T[] {
if (changeType === undefined) {
// no changes requested, do nothing
return values
}
if (changeType) {
// insert to the existing collection
let i = 0
for (; i < values.length; i++) {
if (values[i].name.localeCompare(element.name) >= 0) {
break
}
}
const retVal = [...values]
retVal.splice(i, 0, element)
return retVal
}
// remove from existing collection
return values.filter(({name}) => name !== element.name)
}

View File

@ -0,0 +1,68 @@
import {
computeNamedChanges,
changeNamedCollection,
} from 'src/admin/util/changeNamedCollection'
describe('admin/util/changeNamedCollection', () => {
describe('computeNamedChanges', () => {
it('returns undefined upon no change', () => {
expect(computeNamedChanges([], [])).toBe(undefined)
expect(computeNamedChanges([{name: 'a'}], [{name: 'a'}])).toBe(undefined)
})
it('returns value indicating changes', () => {
expect(
computeNamedChanges([{name: 'a'}, {name: 'b'}], [{name: 'a'}])
).toEqual({b: false})
expect(
computeNamedChanges([{name: 'a'}], [{name: 'a'}, {name: 'b'}])
).toEqual({b: true})
expect(
computeNamedChanges(
[{name: 'a'}, {name: 'c'}],
[{name: 'a'}, {name: 'b'}]
)
).toEqual({b: true, c: false})
})
})
describe('changeNamedCollection', () => {
it('changes nothing upon no change', () => {
const collection = [{name: 'a'}, {name: 'b'}]
expect(changeNamedCollection(collection, {name: 'c'}, undefined)).toBe(
collection
)
})
it('removes from collection', () => {
const collection = [{name: 'a'}, {name: 'b'}]
expect(changeNamedCollection(collection, {name: 'a'}, false)).toEqual([
{name: 'b'},
])
expect(changeNamedCollection(collection, {name: 'b'}, false)).toEqual([
{name: 'a'},
])
expect(changeNamedCollection(collection, {name: 'c'}, false)).toEqual(
collection
)
})
it('adds to collection', () => {
const collection = [{name: 'b'}, {name: 'd'}]
expect(changeNamedCollection([], {name: 'a'}, true)).toEqual([
{name: 'a'},
])
expect(changeNamedCollection(collection, {name: 'a'}, true)).toEqual([
{name: 'a'},
{name: 'b'},
{name: 'd'},
])
expect(changeNamedCollection(collection, {name: 'c'}, true)).toEqual([
{name: 'b'},
{name: 'c'},
{name: 'd'},
])
expect(changeNamedCollection(collection, {name: 'e'}, true)).toEqual([
{name: 'b'},
{name: 'd'},
{name: 'e'},
])
})
})
})