Skip to content

Commit

Permalink
Add dictionary support to createEntityAdapter many methods (#444)
Browse files Browse the repository at this point in the history
* Add dictionary support to addMany and upsertMany

* Update type tests

* Remove unplanned code

* Do array check and conversion first for clarity

* Add test for setAll on sorted adapter, use EntityId type
  • Loading branch information
msutkowski authored Mar 26, 2020
1 parent f14c0dc commit c89d5e2
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 19 deletions.
18 changes: 12 additions & 6 deletions src/entities/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,22 @@ export interface EntityStateAdapter<T> {
action: PayloadAction<T>
): S

addMany<S extends EntityState<T>>(state: PreventAny<S, T>, entities: T[]): S
addMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
entities: PayloadAction<T[]>
entities: T[] | Record<EntityId, T>
): S
addMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
entities: PayloadAction<T[] | Record<EntityId, T>>
): S

setAll<S extends EntityState<T>>(state: PreventAny<S, T>, entities: T[]): S
setAll<S extends EntityState<T>>(
state: PreventAny<S, T>,
entities: PayloadAction<T[]>
entities: T[] | Record<EntityId, T>
): S
setAll<S extends EntityState<T>>(
state: PreventAny<S, T>,
entities: PayloadAction<T[] | Record<EntityId, T>>
): S

removeOne<S extends EntityState<T>>(state: PreventAny<S, T>, key: EntityId): S
Expand Down Expand Up @@ -112,11 +118,11 @@ export interface EntityStateAdapter<T> {

upsertMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
entities: T[]
entities: T[] | Record<EntityId, T>
): S
upsertMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
entities: PayloadAction<T[]>
entities: PayloadAction<T[] | Record<EntityId, T>>
): S
}

Expand Down
56 changes: 56 additions & 0 deletions src/entities/sorted_state_adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@ describe('Sorted State Adapter', () => {
})
})

it('should let you add many entities to the state from a dictionary', () => {
const withOneEntity = adapter.addOne(state, TheGreatGatsby)

const withManyMore = adapter.addMany(withOneEntity, {
[AClockworkOrange.id]: AClockworkOrange,
[AnimalFarm.id]: AnimalFarm
})

expect(withManyMore).toEqual({
ids: [AClockworkOrange.id, AnimalFarm.id, TheGreatGatsby.id],
entities: {
[TheGreatGatsby.id]: TheGreatGatsby,
[AClockworkOrange.id]: AClockworkOrange,
[AnimalFarm.id]: AnimalFarm
}
})
})

it('should remove existing and add new ones on setAll', () => {
const withOneEntity = adapter.addOne(state, TheGreatGatsby)

Expand All @@ -102,6 +120,23 @@ describe('Sorted State Adapter', () => {
})
})

it('should remove existing and add new ones on setAll when passing in a dictionary', () => {
const withOneEntity = adapter.addOne(state, TheGreatGatsby)

const withAll = adapter.setAll(withOneEntity, {
[AClockworkOrange.id]: AClockworkOrange,
[AnimalFarm.id]: AnimalFarm
})

expect(withAll).toEqual({
ids: [AClockworkOrange.id, AnimalFarm.id],
entities: {
[AClockworkOrange.id]: AClockworkOrange,
[AnimalFarm.id]: AnimalFarm
}
})
})

it('should remove existing and add new ones on addAll (deprecated)', () => {
const withOneEntity = adapter.addOne(state, TheGreatGatsby)

Expand Down Expand Up @@ -380,4 +415,25 @@ describe('Sorted State Adapter', () => {
}
})
})

it('should let you upsert many entities in the state when passing in a dictionary', () => {
const firstChange = { title: 'Zack' }
const withMany = adapter.setAll(state, [TheGreatGatsby])

const withUpserts = adapter.upsertMany(withMany, {
[TheGreatGatsby.id]: { ...TheGreatGatsby, ...firstChange },
[AClockworkOrange.id]: AClockworkOrange
})

expect(withUpserts).toEqual({
ids: [AClockworkOrange.id, TheGreatGatsby.id],
entities: {
[TheGreatGatsby.id]: {
...TheGreatGatsby,
...firstChange
},
[AClockworkOrange.id]: AClockworkOrange
}
})
})
})
26 changes: 22 additions & 4 deletions src/entities/sorted_state_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
IdSelector,
Comparer,
EntityStateAdapter,
Update
Update,
EntityId
} from './models'
import { createStateOperator } from './state_adapter'
import { createUnsortedStateAdapter } from './unsorted_state_adapter'
Expand All @@ -23,7 +24,14 @@ export function createSortedStateAdapter<T>(
return addManyMutably([entity], state)
}

function addManyMutably(newModels: T[], state: R): void {
function addManyMutably(
newModels: T[] | Record<EntityId, T>,
state: R
): void {
if (!Array.isArray(newModels)) {
newModels = Object.values(newModels)
}

const models = newModels.filter(
model => !(selectIdValue(model, selectId) in state.entities)
)
Expand All @@ -33,7 +41,10 @@ export function createSortedStateAdapter<T>(
}
}

function setAllMutably(models: T[], state: R): void {
function setAllMutably(models: T[] | Record<EntityId, T>, state: R): void {
if (!Array.isArray(models)) {
models = Object.values(models)
}
state.entities = {}
state.ids = []

Expand Down Expand Up @@ -74,7 +85,14 @@ export function createSortedStateAdapter<T>(
return upsertManyMutably([entity], state)
}

function upsertManyMutably(entities: T[], state: R): void {
function upsertManyMutably(
entities: T[] | Record<EntityId, T>,
state: R
): void {
if (!Array.isArray(entities)) {
entities = Object.values(entities)
}

const added: T[] = []
const updated: Update<T>[] = []

Expand Down
59 changes: 56 additions & 3 deletions src/entities/unsorted_state_adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,24 @@ describe('Unsorted State Adapter', () => {
})
})

it('should let you add many entities to the state from a dictionary', () => {
const withOneEntity = adapter.addOne(state, TheGreatGatsby)

const withManyMore = adapter.addMany(withOneEntity, {
[AClockworkOrange.id]: AClockworkOrange,
[AnimalFarm.id]: AnimalFarm
})

expect(withManyMore).toEqual({
ids: [TheGreatGatsby.id, AClockworkOrange.id, AnimalFarm.id],
entities: {
[TheGreatGatsby.id]: TheGreatGatsby,
[AClockworkOrange.id]: AClockworkOrange,
[AnimalFarm.id]: AnimalFarm
}
})
})

it('should remove existing and add new ones on setAll', () => {
const withOneEntity = adapter.addOne(state, TheGreatGatsby)

Expand All @@ -86,6 +104,23 @@ describe('Unsorted State Adapter', () => {
})
})

it('should remove existing and add new ones on setAll when passing in a dictionary', () => {
const withOneEntity = adapter.addOne(state, TheGreatGatsby)

const withAll = adapter.setAll(withOneEntity, {
[AClockworkOrange.id]: AClockworkOrange,
[AnimalFarm.id]: AnimalFarm
})

expect(withAll).toEqual({
ids: [AClockworkOrange.id, AnimalFarm.id],
entities: {
[AClockworkOrange.id]: AClockworkOrange,
[AnimalFarm.id]: AnimalFarm
}
})
})

it('should let you add remove an entity from the state', () => {
const withOneEntity = adapter.addOne(state, TheGreatGatsby)

Expand Down Expand Up @@ -230,14 +265,11 @@ describe('Unsorted State Adapter', () => {

/*
Original code failed with a mish-mash of values, like:
{
ids: [ 'c' ],
entities: { b: { id: 'b', title: 'First' }, c: { id: 'c' } }
}
We now expect that only 'c' will be left:
{
ids: [ 'c' ],
entities: { c: { id: 'c', title: 'First' } }
Expand Down Expand Up @@ -299,4 +331,25 @@ describe('Unsorted State Adapter', () => {
}
})
})

it('should let you upsert many entities in the state when passing in a dictionary', () => {
const firstChange = { title: 'Zack' }
const withMany = adapter.setAll(state, [TheGreatGatsby])

const withUpserts = adapter.upsertMany(withMany, {
[TheGreatGatsby.id]: { ...TheGreatGatsby, ...firstChange },
[AClockworkOrange.id]: AClockworkOrange
})

expect(withUpserts).toEqual({
ids: [TheGreatGatsby.id, AClockworkOrange.id],
entities: {
[TheGreatGatsby.id]: {
...TheGreatGatsby,
...firstChange
},
[AClockworkOrange.id]: AClockworkOrange
}
})
})
})
21 changes: 18 additions & 3 deletions src/entities/unsorted_state_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,21 @@ export function createUnsortedStateAdapter<T>(
state.entities[key] = entity
}

function addManyMutably(entities: T[], state: R): void {
function addManyMutably(entities: T[] | Record<EntityId, T>, state: R): void {
if (!Array.isArray(entities)) {
entities = Object.values(entities)
}

for (const entity of entities) {
addOneMutably(entity, state)
}
}

function setAllMutably(entities: T[], state: R): void {
function setAllMutably(entities: T[] | Record<EntityId, T>, state: R): void {
if (!Array.isArray(entities)) {
entities = Object.values(entities)
}

state.ids = []
state.entities = {}

Expand Down Expand Up @@ -123,7 +131,14 @@ export function createUnsortedStateAdapter<T>(
return upsertManyMutably([entity], state)
}

function upsertManyMutably(entities: T[], state: R): void {
function upsertManyMutably(
entities: T[] | Record<EntityId, T>,
state: R
): void {
if (!Array.isArray(entities)) {
entities = Object.values(entities)
}

const added: T[] = []
const updated: Update<T>[] = []

Expand Down
12 changes: 9 additions & 3 deletions type-tests/files/createEntityAdapter.typetest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,12 @@ function extractReducers<T>(
})

expectType<ActionCreatorWithPayload<Entity>>(slice.actions.addOne)
expectType<ActionCreatorWithPayload<Entity[]>>(slice.actions.addMany)
expectType<ActionCreatorWithPayload<Entity[]>>(slice.actions.setAll)
expectType<ActionCreatorWithPayload<Entity[] | Record<string, Entity>>>(
slice.actions.addMany
)
expectType<ActionCreatorWithPayload<Entity[] | Record<string, Entity>>>(
slice.actions.setAll
)
expectType<ActionCreatorWithPayload<EntityId>>(slice.actions.removeOne)
expectType<ActionCreatorWithPayload<EntityId[]>>(slice.actions.removeMany)
expectType<ActionCreatorWithoutPayload>(slice.actions.removeAll)
Expand All @@ -51,7 +55,9 @@ function extractReducers<T>(
slice.actions.updateMany
)
expectType<ActionCreatorWithPayload<Entity>>(slice.actions.upsertOne)
expectType<ActionCreatorWithPayload<Entity[]>>(slice.actions.upsertMany)
expectType<ActionCreatorWithPayload<Entity[] | Record<string, Entity>>>(
slice.actions.upsertMany
)
}

/**
Expand Down

0 comments on commit c89d5e2

Please sign in to comment.