This commit is contained in:
43
node_modules/@reduxjs/toolkit/src/entities/create_adapter.ts
generated
vendored
Normal file
43
node_modules/@reduxjs/toolkit/src/entities/create_adapter.ts
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import type {
|
||||
EntityDefinition,
|
||||
Comparer,
|
||||
IdSelector,
|
||||
EntityAdapter,
|
||||
} from './models'
|
||||
import { createInitialStateFactory } from './entity_state'
|
||||
import { createSelectorsFactory } from './state_selectors'
|
||||
import { createSortedStateAdapter } from './sorted_state_adapter'
|
||||
import { createUnsortedStateAdapter } from './unsorted_state_adapter'
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createEntityAdapter<T>(
|
||||
options: {
|
||||
selectId?: IdSelector<T>
|
||||
sortComparer?: false | Comparer<T>
|
||||
} = {}
|
||||
): EntityAdapter<T> {
|
||||
const { selectId, sortComparer }: EntityDefinition<T> = {
|
||||
sortComparer: false,
|
||||
selectId: (instance: any) => instance.id,
|
||||
...options,
|
||||
}
|
||||
|
||||
const stateFactory = createInitialStateFactory<T>()
|
||||
const selectorsFactory = createSelectorsFactory<T>()
|
||||
const stateAdapter = sortComparer
|
||||
? createSortedStateAdapter(selectId, sortComparer)
|
||||
: createUnsortedStateAdapter(selectId)
|
||||
|
||||
return {
|
||||
selectId,
|
||||
sortComparer,
|
||||
...stateFactory,
|
||||
...selectorsFactory,
|
||||
...stateAdapter,
|
||||
}
|
||||
}
|
||||
20
node_modules/@reduxjs/toolkit/src/entities/entity_state.ts
generated
vendored
Normal file
20
node_modules/@reduxjs/toolkit/src/entities/entity_state.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { EntityState } from './models'
|
||||
|
||||
export function getInitialEntityState<V>(): EntityState<V> {
|
||||
return {
|
||||
ids: [],
|
||||
entities: {},
|
||||
}
|
||||
}
|
||||
|
||||
export function createInitialStateFactory<V>() {
|
||||
function getInitialState(): EntityState<V>
|
||||
function getInitialState<S extends object>(
|
||||
additionalState: S
|
||||
): EntityState<V> & S
|
||||
function getInitialState(additionalState: any = {}): any {
|
||||
return Object.assign(getInitialEntityState(), additionalState)
|
||||
}
|
||||
|
||||
return { getInitialState }
|
||||
}
|
||||
9
node_modules/@reduxjs/toolkit/src/entities/index.ts
generated
vendored
Normal file
9
node_modules/@reduxjs/toolkit/src/entities/index.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
export { createEntityAdapter } from './create_adapter'
|
||||
export type {
|
||||
Dictionary,
|
||||
EntityState,
|
||||
EntityAdapter,
|
||||
Update,
|
||||
IdSelector,
|
||||
Comparer,
|
||||
} from './models'
|
||||
171
node_modules/@reduxjs/toolkit/src/entities/models.ts
generated
vendored
Normal file
171
node_modules/@reduxjs/toolkit/src/entities/models.ts
generated
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
import type { PayloadAction } from '../createAction'
|
||||
import type { IsAny } from '../tsHelpers'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type EntityId = number | string
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type Comparer<T> = (a: T, b: T) => number
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type IdSelector<T> = (model: T) => EntityId
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface DictionaryNum<T> {
|
||||
[id: number]: T | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Dictionary<T> extends DictionaryNum<T> {
|
||||
[id: string]: T | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type Update<T> = { id: EntityId; changes: Partial<T> }
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface EntityState<T> {
|
||||
ids: EntityId[]
|
||||
entities: Dictionary<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface EntityDefinition<T> {
|
||||
selectId: IdSelector<T>
|
||||
sortComparer: false | Comparer<T>
|
||||
}
|
||||
|
||||
export type PreventAny<S, T> = IsAny<S, EntityState<T>, S>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface EntityStateAdapter<T> {
|
||||
addOne<S extends EntityState<T>>(state: PreventAny<S, T>, entity: T): S
|
||||
addOne<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
action: PayloadAction<T>
|
||||
): S
|
||||
|
||||
addMany<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
entities: readonly T[] | Record<EntityId, T>
|
||||
): S
|
||||
addMany<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
|
||||
): S
|
||||
|
||||
setOne<S extends EntityState<T>>(state: PreventAny<S, T>, entity: T): S
|
||||
setOne<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
action: PayloadAction<T>
|
||||
): S
|
||||
setMany<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
entities: readonly T[] | Record<EntityId, T>
|
||||
): S
|
||||
setMany<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
|
||||
): S
|
||||
setAll<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
entities: readonly T[] | Record<EntityId, T>
|
||||
): S
|
||||
setAll<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
|
||||
): S
|
||||
|
||||
removeOne<S extends EntityState<T>>(state: PreventAny<S, T>, key: EntityId): S
|
||||
removeOne<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
key: PayloadAction<EntityId>
|
||||
): S
|
||||
|
||||
removeMany<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
keys: readonly EntityId[]
|
||||
): S
|
||||
removeMany<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
keys: PayloadAction<readonly EntityId[]>
|
||||
): S
|
||||
|
||||
removeAll<S extends EntityState<T>>(state: PreventAny<S, T>): S
|
||||
|
||||
updateOne<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
update: Update<T>
|
||||
): S
|
||||
updateOne<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
update: PayloadAction<Update<T>>
|
||||
): S
|
||||
|
||||
updateMany<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
updates: ReadonlyArray<Update<T>>
|
||||
): S
|
||||
updateMany<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
updates: PayloadAction<ReadonlyArray<Update<T>>>
|
||||
): S
|
||||
|
||||
upsertOne<S extends EntityState<T>>(state: PreventAny<S, T>, entity: T): S
|
||||
upsertOne<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
entity: PayloadAction<T>
|
||||
): S
|
||||
|
||||
upsertMany<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
entities: readonly T[] | Record<EntityId, T>
|
||||
): S
|
||||
upsertMany<S extends EntityState<T>>(
|
||||
state: PreventAny<S, T>,
|
||||
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
|
||||
): S
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface EntitySelectors<T, V> {
|
||||
selectIds: (state: V) => EntityId[]
|
||||
selectEntities: (state: V) => Dictionary<T>
|
||||
selectAll: (state: V) => T[]
|
||||
selectTotal: (state: V) => number
|
||||
selectById: (state: V, id: EntityId) => T | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface EntityAdapter<T> extends EntityStateAdapter<T> {
|
||||
selectId: IdSelector<T>
|
||||
sortComparer: false | Comparer<T>
|
||||
getInitialState(): EntityState<T>
|
||||
getInitialState<S extends object>(state: S): EntityState<T> & S
|
||||
getSelectors(): EntitySelectors<T, EntityState<T>>
|
||||
getSelectors<V>(
|
||||
selectState: (state: V) => EntityState<T>
|
||||
): EntitySelectors<T, V>
|
||||
}
|
||||
168
node_modules/@reduxjs/toolkit/src/entities/sorted_state_adapter.ts
generated
vendored
Normal file
168
node_modules/@reduxjs/toolkit/src/entities/sorted_state_adapter.ts
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
import type {
|
||||
EntityState,
|
||||
IdSelector,
|
||||
Comparer,
|
||||
EntityStateAdapter,
|
||||
Update,
|
||||
EntityId,
|
||||
} from './models'
|
||||
import { createStateOperator } from './state_adapter'
|
||||
import { createUnsortedStateAdapter } from './unsorted_state_adapter'
|
||||
import {
|
||||
selectIdValue,
|
||||
ensureEntitiesArray,
|
||||
splitAddedUpdatedEntities,
|
||||
} from './utils'
|
||||
|
||||
export function createSortedStateAdapter<T>(
|
||||
selectId: IdSelector<T>,
|
||||
sort: Comparer<T>
|
||||
): EntityStateAdapter<T> {
|
||||
type R = EntityState<T>
|
||||
|
||||
const { removeOne, removeMany, removeAll } =
|
||||
createUnsortedStateAdapter(selectId)
|
||||
|
||||
function addOneMutably(entity: T, state: R): void {
|
||||
return addManyMutably([entity], state)
|
||||
}
|
||||
|
||||
function addManyMutably(
|
||||
newEntities: readonly T[] | Record<EntityId, T>,
|
||||
state: R
|
||||
): void {
|
||||
newEntities = ensureEntitiesArray(newEntities)
|
||||
|
||||
const models = newEntities.filter(
|
||||
(model) => !(selectIdValue(model, selectId) in state.entities)
|
||||
)
|
||||
|
||||
if (models.length !== 0) {
|
||||
merge(models, state)
|
||||
}
|
||||
}
|
||||
|
||||
function setOneMutably(entity: T, state: R): void {
|
||||
return setManyMutably([entity], state)
|
||||
}
|
||||
|
||||
function setManyMutably(
|
||||
newEntities: readonly T[] | Record<EntityId, T>,
|
||||
state: R
|
||||
): void {
|
||||
newEntities = ensureEntitiesArray(newEntities)
|
||||
if (newEntities.length !== 0) {
|
||||
merge(newEntities, state)
|
||||
}
|
||||
}
|
||||
|
||||
function setAllMutably(
|
||||
newEntities: readonly T[] | Record<EntityId, T>,
|
||||
state: R
|
||||
): void {
|
||||
newEntities = ensureEntitiesArray(newEntities)
|
||||
state.entities = {}
|
||||
state.ids = []
|
||||
|
||||
addManyMutably(newEntities, state)
|
||||
}
|
||||
|
||||
function updateOneMutably(update: Update<T>, state: R): void {
|
||||
return updateManyMutably([update], state)
|
||||
}
|
||||
|
||||
function updateManyMutably(
|
||||
updates: ReadonlyArray<Update<T>>,
|
||||
state: R
|
||||
): void {
|
||||
let appliedUpdates = false
|
||||
|
||||
for (let update of updates) {
|
||||
const entity = state.entities[update.id]
|
||||
if (!entity) {
|
||||
continue
|
||||
}
|
||||
|
||||
appliedUpdates = true
|
||||
|
||||
Object.assign(entity, update.changes)
|
||||
const newId = selectId(entity)
|
||||
if (update.id !== newId) {
|
||||
delete state.entities[update.id]
|
||||
state.entities[newId] = entity
|
||||
}
|
||||
}
|
||||
|
||||
if (appliedUpdates) {
|
||||
resortEntities(state)
|
||||
}
|
||||
}
|
||||
|
||||
function upsertOneMutably(entity: T, state: R): void {
|
||||
return upsertManyMutably([entity], state)
|
||||
}
|
||||
|
||||
function upsertManyMutably(
|
||||
newEntities: readonly T[] | Record<EntityId, T>,
|
||||
state: R
|
||||
): void {
|
||||
const [added, updated] = splitAddedUpdatedEntities<T>(
|
||||
newEntities,
|
||||
selectId,
|
||||
state
|
||||
)
|
||||
|
||||
updateManyMutably(updated, state)
|
||||
addManyMutably(added, state)
|
||||
}
|
||||
|
||||
function areArraysEqual(a: readonly unknown[], b: readonly unknown[]) {
|
||||
if (a.length !== b.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0; i < a.length && i < b.length; i++) {
|
||||
if (a[i] === b[i]) {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function merge(models: readonly T[], state: R): void {
|
||||
// Insert/overwrite all new/updated
|
||||
models.forEach((model) => {
|
||||
state.entities[selectId(model)] = model
|
||||
})
|
||||
|
||||
resortEntities(state)
|
||||
}
|
||||
|
||||
function resortEntities(state: R) {
|
||||
const allEntities = Object.values(state.entities) as T[]
|
||||
allEntities.sort(sort)
|
||||
|
||||
const newSortedIds = allEntities.map(selectId)
|
||||
const { ids } = state
|
||||
|
||||
if (!areArraysEqual(ids, newSortedIds)) {
|
||||
state.ids = newSortedIds
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
removeOne,
|
||||
removeMany,
|
||||
removeAll,
|
||||
addOne: createStateOperator(addOneMutably),
|
||||
updateOne: createStateOperator(updateOneMutably),
|
||||
upsertOne: createStateOperator(upsertOneMutably),
|
||||
setOne: createStateOperator(setOneMutably),
|
||||
setMany: createStateOperator(setManyMutably),
|
||||
setAll: createStateOperator(setAllMutably),
|
||||
addMany: createStateOperator(addManyMutably),
|
||||
updateMany: createStateOperator(updateManyMutably),
|
||||
upsertMany: createStateOperator(upsertManyMutably),
|
||||
}
|
||||
}
|
||||
57
node_modules/@reduxjs/toolkit/src/entities/state_adapter.ts
generated
vendored
Normal file
57
node_modules/@reduxjs/toolkit/src/entities/state_adapter.ts
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
import createNextState, { isDraft } from 'immer'
|
||||
import type { EntityState, PreventAny } from './models'
|
||||
import type { PayloadAction } from '../createAction'
|
||||
import { isFSA } from '../createAction'
|
||||
import { IsAny } from '../tsHelpers'
|
||||
|
||||
export function createSingleArgumentStateOperator<V>(
|
||||
mutator: (state: EntityState<V>) => void
|
||||
) {
|
||||
const operator = createStateOperator((_: undefined, state: EntityState<V>) =>
|
||||
mutator(state)
|
||||
)
|
||||
|
||||
return function operation<S extends EntityState<V>>(
|
||||
state: PreventAny<S, V>
|
||||
): S {
|
||||
return operator(state as S, undefined)
|
||||
}
|
||||
}
|
||||
|
||||
export function createStateOperator<V, R>(
|
||||
mutator: (arg: R, state: EntityState<V>) => void
|
||||
) {
|
||||
return function operation<S extends EntityState<V>>(
|
||||
state: S,
|
||||
arg: R | PayloadAction<R>
|
||||
): S {
|
||||
function isPayloadActionArgument(
|
||||
arg: R | PayloadAction<R>
|
||||
): arg is PayloadAction<R> {
|
||||
return isFSA(arg)
|
||||
}
|
||||
|
||||
const runMutator = (draft: EntityState<V>) => {
|
||||
if (isPayloadActionArgument(arg)) {
|
||||
mutator(arg.payload, draft)
|
||||
} else {
|
||||
mutator(arg, draft)
|
||||
}
|
||||
}
|
||||
|
||||
if (isDraft(state)) {
|
||||
// we must already be inside a `createNextState` call, likely because
|
||||
// this is being wrapped in `createReducer` or `createSlice`.
|
||||
// It's safe to just pass the draft to the mutator.
|
||||
runMutator(state)
|
||||
|
||||
// since it's a draft, we'll just return it
|
||||
return state
|
||||
} else {
|
||||
// @ts-ignore createNextState() produces an Immutable<Draft<S>> rather
|
||||
// than an Immutable<S>, and TypeScript cannot find out how to reconcile
|
||||
// these two types.
|
||||
return createNextState(state, runMutator)
|
||||
}
|
||||
}
|
||||
}
|
||||
67
node_modules/@reduxjs/toolkit/src/entities/state_selectors.ts
generated
vendored
Normal file
67
node_modules/@reduxjs/toolkit/src/entities/state_selectors.ts
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { Selector } from 'reselect'
|
||||
import { createDraftSafeSelector } from '../createDraftSafeSelector'
|
||||
import type {
|
||||
EntityState,
|
||||
EntitySelectors,
|
||||
Dictionary,
|
||||
EntityId,
|
||||
} from './models'
|
||||
|
||||
export function createSelectorsFactory<T>() {
|
||||
function getSelectors(): EntitySelectors<T, EntityState<T>>
|
||||
function getSelectors<V>(
|
||||
selectState: (state: V) => EntityState<T>
|
||||
): EntitySelectors<T, V>
|
||||
function getSelectors<V>(
|
||||
selectState?: (state: V) => EntityState<T>
|
||||
): EntitySelectors<T, any> {
|
||||
const selectIds = (state: EntityState<T>) => state.ids
|
||||
|
||||
const selectEntities = (state: EntityState<T>) => state.entities
|
||||
|
||||
const selectAll = createDraftSafeSelector(
|
||||
selectIds,
|
||||
selectEntities,
|
||||
(ids, entities): T[] => ids.map((id) => entities[id]!)
|
||||
)
|
||||
|
||||
const selectId = (_: unknown, id: EntityId) => id
|
||||
|
||||
const selectById = (entities: Dictionary<T>, id: EntityId) => entities[id]
|
||||
|
||||
const selectTotal = createDraftSafeSelector(selectIds, (ids) => ids.length)
|
||||
|
||||
if (!selectState) {
|
||||
return {
|
||||
selectIds,
|
||||
selectEntities,
|
||||
selectAll,
|
||||
selectTotal,
|
||||
selectById: createDraftSafeSelector(
|
||||
selectEntities,
|
||||
selectId,
|
||||
selectById
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const selectGlobalizedEntities = createDraftSafeSelector(
|
||||
selectState as Selector<V, EntityState<T>>,
|
||||
selectEntities
|
||||
)
|
||||
|
||||
return {
|
||||
selectIds: createDraftSafeSelector(selectState, selectIds),
|
||||
selectEntities: selectGlobalizedEntities,
|
||||
selectAll: createDraftSafeSelector(selectState, selectAll),
|
||||
selectTotal: createDraftSafeSelector(selectState, selectTotal),
|
||||
selectById: createDraftSafeSelector(
|
||||
selectGlobalizedEntities,
|
||||
selectId,
|
||||
selectById
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
return { getSelectors }
|
||||
}
|
||||
83
node_modules/@reduxjs/toolkit/src/entities/tests/entity_state.test.ts
generated
vendored
Normal file
83
node_modules/@reduxjs/toolkit/src/entities/tests/entity_state.test.ts
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { EntityAdapter } from '../index'
|
||||
import { createEntityAdapter } from '../index'
|
||||
import type { PayloadAction } from '../../createAction'
|
||||
import { createAction } from '../../createAction'
|
||||
import { createSlice } from '../../createSlice'
|
||||
import type { BookModel } from './fixtures/book'
|
||||
|
||||
describe('Entity State', () => {
|
||||
let adapter: EntityAdapter<BookModel>
|
||||
|
||||
beforeEach(() => {
|
||||
adapter = createEntityAdapter({
|
||||
selectId: (book: BookModel) => book.id,
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you get the initial state', () => {
|
||||
const initialState = adapter.getInitialState()
|
||||
|
||||
expect(initialState).toEqual({
|
||||
ids: [],
|
||||
entities: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you provide additional initial state properties', () => {
|
||||
const additionalProperties = { isHydrated: true }
|
||||
|
||||
const initialState = adapter.getInitialState(additionalProperties)
|
||||
|
||||
expect(initialState).toEqual({
|
||||
...additionalProperties,
|
||||
ids: [],
|
||||
entities: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow methods to be passed as reducers', () => {
|
||||
const upsertBook = createAction<BookModel>('otherBooks/upsert')
|
||||
|
||||
const booksSlice = createSlice({
|
||||
name: 'books',
|
||||
initialState: adapter.getInitialState(),
|
||||
reducers: {
|
||||
addOne: adapter.addOne,
|
||||
removeOne(state, action: PayloadAction<string>) {
|
||||
// TODO The nested `produce` calls don't mutate `state` here as I would have expected.
|
||||
// TODO (note that `state` here is actually an Immer Draft<S>, from `createReducer`)
|
||||
// TODO However, this works if we _return_ the new plain result value instead
|
||||
// TODO See https://github.com/immerjs/immer/issues/533
|
||||
const result = adapter.removeOne(state, action)
|
||||
return result
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(upsertBook, (state, action) => {
|
||||
return adapter.upsertOne(state, action)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const { addOne, removeOne } = booksSlice.actions
|
||||
const { reducer } = booksSlice
|
||||
|
||||
const selectors = adapter.getSelectors()
|
||||
|
||||
const book1: BookModel = { id: 'a', title: 'First' }
|
||||
const book1a: BookModel = { id: 'a', title: 'Second' }
|
||||
|
||||
const afterAddOne = reducer(undefined, addOne(book1))
|
||||
expect(afterAddOne.entities[book1.id]).toBe(book1)
|
||||
|
||||
const afterRemoveOne = reducer(afterAddOne, removeOne(book1.id))
|
||||
expect(afterRemoveOne.entities[book1.id]).toBeUndefined()
|
||||
expect(selectors.selectTotal(afterRemoveOne)).toBe(0)
|
||||
|
||||
const afterUpsertFirst = reducer(afterRemoveOne, upsertBook(book1))
|
||||
const afterUpsertSecond = reducer(afterUpsertFirst, upsertBook(book1a))
|
||||
|
||||
expect(afterUpsertSecond.entities[book1.id]).toEqual(book1a)
|
||||
expect(selectors.selectTotal(afterUpsertSecond)).toBe(1)
|
||||
})
|
||||
})
|
||||
26
node_modules/@reduxjs/toolkit/src/entities/tests/fixtures/book.ts
generated
vendored
Normal file
26
node_modules/@reduxjs/toolkit/src/entities/tests/fixtures/book.ts
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
export interface BookModel {
|
||||
id: string
|
||||
title: string
|
||||
author?: string
|
||||
}
|
||||
|
||||
export const AClockworkOrange: BookModel = Object.freeze({
|
||||
id: 'aco',
|
||||
title: 'A Clockwork Orange',
|
||||
})
|
||||
|
||||
export const AnimalFarm: BookModel = Object.freeze({
|
||||
id: 'af',
|
||||
title: 'Animal Farm',
|
||||
})
|
||||
|
||||
export const TheGreatGatsby: BookModel = Object.freeze({
|
||||
id: 'tgg',
|
||||
title: 'The Great Gatsby',
|
||||
})
|
||||
|
||||
export const TheHobbit: BookModel = Object.freeze({
|
||||
id: 'th',
|
||||
title: 'The Hobbit',
|
||||
author: 'J. R. R. Tolkien',
|
||||
})
|
||||
932
node_modules/@reduxjs/toolkit/src/entities/tests/sorted_state_adapter.test.ts
generated
vendored
Normal file
932
node_modules/@reduxjs/toolkit/src/entities/tests/sorted_state_adapter.test.ts
generated
vendored
Normal file
@@ -0,0 +1,932 @@
|
||||
import type { EntityAdapter, EntityState } from '../models'
|
||||
import { createEntityAdapter } from '../create_adapter'
|
||||
import { createAction, createSlice, configureStore } from '@reduxjs/toolkit'
|
||||
import type { BookModel } from './fixtures/book'
|
||||
import {
|
||||
TheGreatGatsby,
|
||||
AClockworkOrange,
|
||||
AnimalFarm,
|
||||
TheHobbit,
|
||||
} from './fixtures/book'
|
||||
import { createNextState } from '../..'
|
||||
|
||||
describe('Sorted State Adapter', () => {
|
||||
let adapter: EntityAdapter<BookModel>
|
||||
let state: EntityState<BookModel>
|
||||
|
||||
beforeAll(() => {
|
||||
//eslint-disable-next-line
|
||||
Object.defineProperty(Array.prototype, 'unwantedField', {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
value: 'This should not appear anywhere',
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
delete (Array.prototype as any).unwantedField
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
adapter = createEntityAdapter({
|
||||
selectId: (book: BookModel) => book.id,
|
||||
sortComparer: (a, b) => {
|
||||
return a.title.localeCompare(b.title)
|
||||
},
|
||||
})
|
||||
|
||||
state = { ids: [], entities: {} }
|
||||
})
|
||||
|
||||
it('should let you add one entity to the state', () => {
|
||||
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
|
||||
|
||||
expect(withOneEntity).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: TheGreatGatsby,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you add one entity to the state as an FSA', () => {
|
||||
const bookAction = createAction<BookModel>('books/add')
|
||||
const withOneEntity = adapter.addOne(state, bookAction(TheGreatGatsby))
|
||||
|
||||
expect(withOneEntity).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: TheGreatGatsby,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should not change state if you attempt to re-add an entity', () => {
|
||||
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
|
||||
|
||||
const readded = adapter.addOne(withOneEntity, TheGreatGatsby)
|
||||
|
||||
expect(readded).toBe(withOneEntity)
|
||||
})
|
||||
|
||||
it('should let you add many entities to the state', () => {
|
||||
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
|
||||
|
||||
const withManyMore = adapter.addMany(withOneEntity, [
|
||||
AClockworkOrange,
|
||||
AnimalFarm,
|
||||
])
|
||||
|
||||
expect(withManyMore).toEqual({
|
||||
ids: [AClockworkOrange.id, AnimalFarm.id, TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: TheGreatGatsby,
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
[AnimalFarm.id]: AnimalFarm,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
const withAll = adapter.setAll(withOneEntity, [
|
||||
AClockworkOrange,
|
||||
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 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)
|
||||
|
||||
const withAll = adapter.setAll(withOneEntity, [
|
||||
AClockworkOrange,
|
||||
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)
|
||||
|
||||
const withoutOne = adapter.removeOne(withOneEntity, TheGreatGatsby.id)
|
||||
|
||||
expect(withoutOne).toEqual({
|
||||
ids: [],
|
||||
entities: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you remove many entities by id from the state', () => {
|
||||
const withAll = adapter.setAll(state, [
|
||||
TheGreatGatsby,
|
||||
AClockworkOrange,
|
||||
AnimalFarm,
|
||||
])
|
||||
|
||||
const withoutMany = adapter.removeMany(withAll, [
|
||||
TheGreatGatsby.id,
|
||||
AClockworkOrange.id,
|
||||
])
|
||||
|
||||
expect(withoutMany).toEqual({
|
||||
ids: [AnimalFarm.id],
|
||||
entities: {
|
||||
[AnimalFarm.id]: AnimalFarm,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you remove all entities from the state', () => {
|
||||
const withAll = adapter.setAll(state, [
|
||||
TheGreatGatsby,
|
||||
AClockworkOrange,
|
||||
AnimalFarm,
|
||||
])
|
||||
|
||||
const withoutAll = adapter.removeAll(withAll)
|
||||
|
||||
expect(withoutAll).toEqual({
|
||||
ids: [],
|
||||
entities: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you update an entity in the state', () => {
|
||||
const withOne = adapter.addOne(state, TheGreatGatsby)
|
||||
const changes = { title: 'A New Hope' }
|
||||
|
||||
const withUpdates = adapter.updateOne(withOne, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes,
|
||||
})
|
||||
|
||||
expect(withUpdates).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: {
|
||||
...TheGreatGatsby,
|
||||
...changes,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should not change state if you attempt to update an entity that has not been added', () => {
|
||||
const withUpdates = adapter.updateOne(state, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes: { title: 'A New Title' },
|
||||
})
|
||||
|
||||
expect(withUpdates).toBe(state)
|
||||
})
|
||||
|
||||
it('Replaces an existing entity if you change the ID while updating', () => {
|
||||
const withAdded = adapter.setAll(state, [
|
||||
{ id: 'a', title: 'First' },
|
||||
{ id: 'b', title: 'Second' },
|
||||
{ id: 'c', title: 'Third' },
|
||||
])
|
||||
|
||||
const withUpdated = adapter.updateOne(withAdded, {
|
||||
id: 'b',
|
||||
changes: {
|
||||
id: 'c',
|
||||
},
|
||||
})
|
||||
|
||||
const { ids, entities } = withUpdated
|
||||
|
||||
expect(ids.length).toBe(2)
|
||||
expect(entities.a).toBeTruthy()
|
||||
expect(entities.b).not.toBeTruthy()
|
||||
expect(entities.c).toBeTruthy()
|
||||
expect(entities.c!.id).toBe('c')
|
||||
expect(entities.c!.title).toBe('Second')
|
||||
})
|
||||
|
||||
it('should not change ids state if you attempt to update an entity that does not impact sorting', () => {
|
||||
const withAll = adapter.setAll(state, [
|
||||
TheGreatGatsby,
|
||||
AClockworkOrange,
|
||||
AnimalFarm,
|
||||
])
|
||||
const changes = { title: 'The Great Gatsby II' }
|
||||
|
||||
const withUpdates = adapter.updateOne(withAll, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes,
|
||||
})
|
||||
|
||||
expect(withAll.ids).toBe(withUpdates.ids)
|
||||
})
|
||||
|
||||
it('should let you update the id of entity', () => {
|
||||
const withOne = adapter.addOne(state, TheGreatGatsby)
|
||||
const changes = { id: 'A New Id' }
|
||||
|
||||
const withUpdates = adapter.updateOne(withOne, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes,
|
||||
})
|
||||
|
||||
expect(withUpdates).toEqual({
|
||||
ids: [changes.id],
|
||||
entities: {
|
||||
[changes.id]: {
|
||||
...TheGreatGatsby,
|
||||
...changes,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should resort correctly if same id but sort key update', () => {
|
||||
const withAll = adapter.setAll(state, [
|
||||
TheGreatGatsby,
|
||||
AnimalFarm,
|
||||
AClockworkOrange,
|
||||
])
|
||||
const changes = { title: 'A New Hope' }
|
||||
|
||||
const withUpdates = adapter.updateOne(withAll, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes,
|
||||
})
|
||||
|
||||
expect(withUpdates).toEqual({
|
||||
ids: [AClockworkOrange.id, TheGreatGatsby.id, AnimalFarm.id],
|
||||
entities: {
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
[TheGreatGatsby.id]: {
|
||||
...TheGreatGatsby,
|
||||
...changes,
|
||||
},
|
||||
[AnimalFarm.id]: AnimalFarm,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should resort correctly if the id and sort key update', () => {
|
||||
const withOne = adapter.setAll(state, [
|
||||
TheGreatGatsby,
|
||||
AnimalFarm,
|
||||
AClockworkOrange,
|
||||
])
|
||||
const changes = { id: 'A New Id', title: 'A New Hope' }
|
||||
|
||||
const withUpdates = adapter.updateOne(withOne, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes,
|
||||
})
|
||||
|
||||
expect(withUpdates).toEqual({
|
||||
ids: [AClockworkOrange.id, changes.id, AnimalFarm.id],
|
||||
entities: {
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
[changes.id]: {
|
||||
...TheGreatGatsby,
|
||||
...changes,
|
||||
},
|
||||
[AnimalFarm.id]: AnimalFarm,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should maintain a stable sorting order when updating items', () => {
|
||||
interface OrderedEntity {
|
||||
id: string
|
||||
order: number
|
||||
ts: number
|
||||
}
|
||||
const sortedItemsAdapter = createEntityAdapter<OrderedEntity>({
|
||||
sortComparer: (a, b) => a.order - b.order,
|
||||
})
|
||||
const withInitialItems = sortedItemsAdapter.setAll(
|
||||
sortedItemsAdapter.getInitialState(),
|
||||
[
|
||||
{ id: 'A', order: 1, ts: 0 },
|
||||
{ id: 'B', order: 2, ts: 0 },
|
||||
{ id: 'C', order: 3, ts: 0 },
|
||||
{ id: 'D', order: 3, ts: 0 },
|
||||
{ id: 'E', order: 3, ts: 0 },
|
||||
]
|
||||
)
|
||||
|
||||
const updated = sortedItemsAdapter.updateOne(withInitialItems, {
|
||||
id: 'C',
|
||||
changes: { ts: 5 },
|
||||
})
|
||||
|
||||
expect(updated.ids).toEqual(['A', 'B', 'C', 'D', 'E'])
|
||||
})
|
||||
|
||||
it('should let you update many entities by id in the state', () => {
|
||||
const firstChange = { title: 'Zack' }
|
||||
const secondChange = { title: 'Aaron' }
|
||||
const withMany = adapter.setAll(state, [TheGreatGatsby, AClockworkOrange])
|
||||
|
||||
const withUpdates = adapter.updateMany(withMany, [
|
||||
{ id: TheGreatGatsby.id, changes: firstChange },
|
||||
{ id: AClockworkOrange.id, changes: secondChange },
|
||||
])
|
||||
|
||||
expect(withUpdates).toEqual({
|
||||
ids: [AClockworkOrange.id, TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: {
|
||||
...TheGreatGatsby,
|
||||
...firstChange,
|
||||
},
|
||||
[AClockworkOrange.id]: {
|
||||
...AClockworkOrange,
|
||||
...secondChange,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you add one entity to the state with upsert()', () => {
|
||||
const withOneEntity = adapter.upsertOne(state, TheGreatGatsby)
|
||||
expect(withOneEntity).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: TheGreatGatsby,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you update an entity in the state with upsert()', () => {
|
||||
const withOne = adapter.addOne(state, TheGreatGatsby)
|
||||
const changes = { title: 'A New Hope' }
|
||||
|
||||
const withUpdates = adapter.upsertOne(withOne, {
|
||||
...TheGreatGatsby,
|
||||
...changes,
|
||||
})
|
||||
expect(withUpdates).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: {
|
||||
...TheGreatGatsby,
|
||||
...changes,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you upsert many entities in the state', () => {
|
||||
const firstChange = { title: 'Zack' }
|
||||
const withMany = adapter.setAll(state, [TheGreatGatsby])
|
||||
|
||||
const withUpserts = adapter.upsertMany(withMany, [
|
||||
{ ...TheGreatGatsby, ...firstChange },
|
||||
AClockworkOrange,
|
||||
])
|
||||
|
||||
expect(withUpserts).toEqual({
|
||||
ids: [AClockworkOrange.id, TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: {
|
||||
...TheGreatGatsby,
|
||||
...firstChange,
|
||||
},
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should do nothing when upsertMany is given an empty array', () => {
|
||||
const withMany = adapter.setAll(state, [TheGreatGatsby])
|
||||
|
||||
const withUpserts = adapter.upsertMany(withMany, [])
|
||||
|
||||
expect(withUpserts).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: TheGreatGatsby,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw when upsertMany is passed undefined or null', async () => {
|
||||
const withMany = adapter.setAll(state, [TheGreatGatsby])
|
||||
|
||||
const fakeRequest = (response: null | undefined) =>
|
||||
new Promise((resolve) => setTimeout(() => resolve(response), 50))
|
||||
|
||||
const undefinedBooks = (await fakeRequest(undefined)) as BookModel[]
|
||||
expect(() => adapter.upsertMany(withMany, undefinedBooks)).toThrow()
|
||||
|
||||
const nullBooks = (await fakeRequest(null)) as BookModel[]
|
||||
expect(() => adapter.upsertMany(withMany, nullBooks)).toThrow()
|
||||
})
|
||||
|
||||
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,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you add a new entity in the state with setOne() and keep the sorting', () => {
|
||||
const withMany = adapter.setAll(state, [AnimalFarm, TheHobbit])
|
||||
const withOneMore = adapter.setOne(withMany, TheGreatGatsby)
|
||||
expect(withOneMore).toEqual({
|
||||
ids: [AnimalFarm.id, TheGreatGatsby.id, TheHobbit.id],
|
||||
entities: {
|
||||
[AnimalFarm.id]: AnimalFarm,
|
||||
[TheHobbit.id]: TheHobbit,
|
||||
[TheGreatGatsby.id]: TheGreatGatsby,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you replace an entity in the state with setOne()', () => {
|
||||
let withOne = adapter.setOne(state, TheHobbit)
|
||||
const changeWithoutAuthor = { id: TheHobbit.id, title: 'Silmarillion' }
|
||||
withOne = adapter.setOne(withOne, changeWithoutAuthor)
|
||||
|
||||
expect(withOne).toEqual({
|
||||
ids: [TheHobbit.id],
|
||||
entities: {
|
||||
[TheHobbit.id]: changeWithoutAuthor,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should do nothing when setMany is given an empty array', () => {
|
||||
const withMany = adapter.setAll(state, [TheGreatGatsby])
|
||||
|
||||
const withUpserts = adapter.setMany(withMany, [])
|
||||
|
||||
expect(withUpserts).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: TheGreatGatsby,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you set many entities in the state', () => {
|
||||
const firstChange = { id: TheHobbit.id, title: 'Silmarillion' }
|
||||
const withMany = adapter.setAll(state, [TheHobbit])
|
||||
|
||||
const withSetMany = adapter.setMany(withMany, [
|
||||
firstChange,
|
||||
AClockworkOrange,
|
||||
])
|
||||
|
||||
expect(withSetMany).toEqual({
|
||||
ids: [AClockworkOrange.id, TheHobbit.id],
|
||||
entities: {
|
||||
[TheHobbit.id]: firstChange,
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you set many entities in the state when passing in a dictionary', () => {
|
||||
const changeWithoutAuthor = { id: TheHobbit.id, title: 'Silmarillion' }
|
||||
const withMany = adapter.setAll(state, [TheHobbit])
|
||||
|
||||
const withSetMany = adapter.setMany(withMany, {
|
||||
[TheHobbit.id]: changeWithoutAuthor,
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
})
|
||||
|
||||
expect(withSetMany).toEqual({
|
||||
ids: [AClockworkOrange.id, TheHobbit.id],
|
||||
entities: {
|
||||
[TheHobbit.id]: changeWithoutAuthor,
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("only returns one entry for that id in the id's array", () => {
|
||||
const book1: BookModel = { id: 'a', title: 'First' }
|
||||
const book2: BookModel = { id: 'b', title: 'Second' }
|
||||
const initialState = adapter.getInitialState()
|
||||
const withItems = adapter.addMany(initialState, [book1, book2])
|
||||
|
||||
expect(withItems.ids).toEqual(['a', 'b'])
|
||||
const withUpdate = adapter.updateOne(withItems, {
|
||||
id: 'a',
|
||||
changes: { id: 'b' },
|
||||
})
|
||||
|
||||
expect(withUpdate.ids).toEqual(['b'])
|
||||
expect(withUpdate.entities['b']!.title).toBe(book1.title)
|
||||
})
|
||||
|
||||
describe('can be used mutably when wrapped in createNextState', () => {
|
||||
test('removeAll', () => {
|
||||
const withTwo = adapter.addMany(state, [TheGreatGatsby, AnimalFarm])
|
||||
const result = createNextState(withTwo, (draft) => {
|
||||
adapter.removeAll(draft)
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {},
|
||||
"ids": Array [],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('addOne', () => {
|
||||
const result = createNextState(state, (draft) => {
|
||||
adapter.addOne(draft, TheGreatGatsby)
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "The Great Gatsby",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('addMany', () => {
|
||||
const result = createNextState(state, (draft) => {
|
||||
adapter.addMany(draft, [TheGreatGatsby, AnimalFarm])
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"af": Object {
|
||||
"id": "af",
|
||||
"title": "Animal Farm",
|
||||
},
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "The Great Gatsby",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"af",
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('setAll', () => {
|
||||
const result = createNextState(state, (draft) => {
|
||||
adapter.setAll(draft, [TheGreatGatsby, AnimalFarm])
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"af": Object {
|
||||
"id": "af",
|
||||
"title": "Animal Farm",
|
||||
},
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "The Great Gatsby",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"af",
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('updateOne', () => {
|
||||
const withOne = adapter.addOne(state, TheGreatGatsby)
|
||||
const changes = { title: 'A New Hope' }
|
||||
const result = createNextState(withOne, (draft) => {
|
||||
adapter.updateOne(draft, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes,
|
||||
})
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "A New Hope",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('updateMany', () => {
|
||||
const firstChange = { title: 'First Change' }
|
||||
const secondChange = { title: 'Second Change' }
|
||||
const thirdChange = { title: 'Third Change' }
|
||||
const fourthChange = { author: 'Fourth Change' }
|
||||
const withMany = adapter.setAll(state, [
|
||||
TheGreatGatsby,
|
||||
AClockworkOrange,
|
||||
TheHobbit,
|
||||
])
|
||||
|
||||
const result = createNextState(withMany, (draft) => {
|
||||
adapter.updateMany(draft, [
|
||||
{ id: TheHobbit.id, changes: firstChange },
|
||||
{ id: TheGreatGatsby.id, changes: secondChange },
|
||||
{ id: AClockworkOrange.id, changes: thirdChange },
|
||||
{ id: TheHobbit.id, changes: fourthChange },
|
||||
])
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"aco": Object {
|
||||
"id": "aco",
|
||||
"title": "Third Change",
|
||||
},
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "Second Change",
|
||||
},
|
||||
"th": Object {
|
||||
"author": "Fourth Change",
|
||||
"id": "th",
|
||||
"title": "First Change",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"th",
|
||||
"tgg",
|
||||
"aco",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('upsertOne (insert)', () => {
|
||||
const result = createNextState(state, (draft) => {
|
||||
adapter.upsertOne(draft, TheGreatGatsby)
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "The Great Gatsby",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('upsertOne (update)', () => {
|
||||
const withOne = adapter.upsertOne(state, TheGreatGatsby)
|
||||
const result = createNextState(withOne, (draft) => {
|
||||
adapter.upsertOne(draft, {
|
||||
id: TheGreatGatsby.id,
|
||||
title: 'A New Hope',
|
||||
})
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "A New Hope",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('upsertMany', () => {
|
||||
const withOne = adapter.upsertOne(state, TheGreatGatsby)
|
||||
const result = createNextState(withOne, (draft) => {
|
||||
adapter.upsertMany(draft, [
|
||||
{
|
||||
id: TheGreatGatsby.id,
|
||||
title: 'A New Hope',
|
||||
},
|
||||
AnimalFarm,
|
||||
])
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"af": Object {
|
||||
"id": "af",
|
||||
"title": "Animal Farm",
|
||||
},
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "A New Hope",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
"af",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('setOne (insert)', () => {
|
||||
const result = createNextState(state, (draft) => {
|
||||
adapter.setOne(draft, TheGreatGatsby)
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "The Great Gatsby",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('setOne (update)', () => {
|
||||
const withOne = adapter.setOne(state, TheHobbit)
|
||||
const result = createNextState(withOne, (draft) => {
|
||||
adapter.setOne(draft, {
|
||||
id: TheHobbit.id,
|
||||
title: 'Silmarillion',
|
||||
})
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"th": Object {
|
||||
"id": "th",
|
||||
"title": "Silmarillion",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"th",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('setMany', () => {
|
||||
const withOne = adapter.setOne(state, TheHobbit)
|
||||
const result = createNextState(withOne, (draft) => {
|
||||
adapter.setMany(draft, [
|
||||
{
|
||||
id: TheHobbit.id,
|
||||
title: 'Silmarillion',
|
||||
},
|
||||
AnimalFarm,
|
||||
])
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"af": Object {
|
||||
"id": "af",
|
||||
"title": "Animal Farm",
|
||||
},
|
||||
"th": Object {
|
||||
"id": "th",
|
||||
"title": "Silmarillion",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"af",
|
||||
"th",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('removeOne', () => {
|
||||
const withTwo = adapter.addMany(state, [TheGreatGatsby, AnimalFarm])
|
||||
const result = createNextState(withTwo, (draft) => {
|
||||
adapter.removeOne(draft, TheGreatGatsby.id)
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"af": Object {
|
||||
"id": "af",
|
||||
"title": "Animal Farm",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"af",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('removeMany', () => {
|
||||
const withThree = adapter.addMany(state, [
|
||||
TheGreatGatsby,
|
||||
AnimalFarm,
|
||||
AClockworkOrange,
|
||||
])
|
||||
const result = createNextState(withThree, (draft) => {
|
||||
adapter.removeMany(draft, [TheGreatGatsby.id, AnimalFarm.id])
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"aco": Object {
|
||||
"id": "aco",
|
||||
"title": "A Clockwork Orange",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"aco",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
61
node_modules/@reduxjs/toolkit/src/entities/tests/state_adapter.test.ts
generated
vendored
Normal file
61
node_modules/@reduxjs/toolkit/src/entities/tests/state_adapter.test.ts
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { EntityAdapter } from '../index'
|
||||
import { createEntityAdapter } from '../index'
|
||||
import type { PayloadAction } from '../../createAction'
|
||||
import { configureStore } from '../../configureStore'
|
||||
import { createSlice } from '../../createSlice'
|
||||
import type { BookModel } from './fixtures/book'
|
||||
|
||||
describe('createStateOperator', () => {
|
||||
let adapter: EntityAdapter<BookModel>
|
||||
|
||||
beforeEach(() => {
|
||||
adapter = createEntityAdapter({
|
||||
selectId: (book: BookModel) => book.id,
|
||||
})
|
||||
})
|
||||
it('Correctly mutates a draft state when inside `createNextState', () => {
|
||||
const booksSlice = createSlice({
|
||||
name: 'books',
|
||||
initialState: adapter.getInitialState(),
|
||||
reducers: {
|
||||
// We should be able to call an adapter method as a mutating helper in a larger reducer
|
||||
addOne(state, action: PayloadAction<BookModel>) {
|
||||
// Originally, having nested `produce` calls don't mutate `state` here as I would have expected.
|
||||
// (note that `state` here is actually an Immer Draft<S>, from `createReducer`)
|
||||
// One woarkound was to return the new plain result value instead
|
||||
// See https://github.com/immerjs/immer/issues/533
|
||||
// However, after tweaking `createStateOperator` to check if the argument is a draft,
|
||||
// we can just treat the operator as strictly mutating, without returning a result,
|
||||
// and the result should be correct.
|
||||
const result = adapter.addOne(state, action)
|
||||
expect(result.ids.length).toBe(1)
|
||||
//Deliberately _don't_ return result
|
||||
},
|
||||
// We should also be able to pass them individually as case reducers
|
||||
addAnother: adapter.addOne,
|
||||
},
|
||||
})
|
||||
|
||||
const { addOne, addAnother } = booksSlice.actions
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
books: booksSlice.reducer,
|
||||
},
|
||||
})
|
||||
|
||||
const book1: BookModel = { id: 'a', title: 'First' }
|
||||
store.dispatch(addOne(book1))
|
||||
|
||||
const state1 = store.getState()
|
||||
expect(state1.books.ids.length).toBe(1)
|
||||
expect(state1.books.entities['a']).toBe(book1)
|
||||
|
||||
const book2: BookModel = { id: 'b', title: 'Second' }
|
||||
store.dispatch(addAnother(book2))
|
||||
|
||||
const state2 = store.getState()
|
||||
expect(state2.books.ids.length).toBe(2)
|
||||
expect(state2.books.entities['b']).toBe(book2)
|
||||
})
|
||||
})
|
||||
129
node_modules/@reduxjs/toolkit/src/entities/tests/state_selectors.test.ts
generated
vendored
Normal file
129
node_modules/@reduxjs/toolkit/src/entities/tests/state_selectors.test.ts
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
import type { EntityAdapter, EntityState } from '../index'
|
||||
import { createEntityAdapter } from '../index'
|
||||
import type { EntitySelectors } from '../models'
|
||||
import type { BookModel } from './fixtures/book'
|
||||
import { AClockworkOrange, AnimalFarm, TheGreatGatsby } from './fixtures/book'
|
||||
import type { Selector } from 'reselect'
|
||||
import { createSelector } from 'reselect'
|
||||
|
||||
describe('Entity State Selectors', () => {
|
||||
describe('Composed Selectors', () => {
|
||||
interface State {
|
||||
books: EntityState<BookModel>
|
||||
}
|
||||
|
||||
let adapter: EntityAdapter<BookModel>
|
||||
let selectors: EntitySelectors<BookModel, State>
|
||||
let state: State
|
||||
|
||||
beforeEach(() => {
|
||||
adapter = createEntityAdapter({
|
||||
selectId: (book: BookModel) => book.id,
|
||||
})
|
||||
|
||||
state = {
|
||||
books: adapter.setAll(adapter.getInitialState(), [
|
||||
AClockworkOrange,
|
||||
AnimalFarm,
|
||||
TheGreatGatsby,
|
||||
]),
|
||||
}
|
||||
|
||||
selectors = adapter.getSelectors((state: State) => state.books)
|
||||
})
|
||||
|
||||
it('should create a selector for selecting the ids', () => {
|
||||
const ids = selectors.selectIds(state)
|
||||
|
||||
expect(ids).toEqual(state.books.ids)
|
||||
})
|
||||
|
||||
it('should create a selector for selecting the entities', () => {
|
||||
const entities = selectors.selectEntities(state)
|
||||
|
||||
expect(entities).toEqual(state.books.entities)
|
||||
})
|
||||
|
||||
it('should create a selector for selecting the list of models', () => {
|
||||
const models = selectors.selectAll(state)
|
||||
|
||||
expect(models).toEqual([AClockworkOrange, AnimalFarm, TheGreatGatsby])
|
||||
})
|
||||
|
||||
it('should create a selector for selecting the count of models', () => {
|
||||
const total = selectors.selectTotal(state)
|
||||
|
||||
expect(total).toEqual(3)
|
||||
})
|
||||
|
||||
it('should create a selector for selecting a single item by ID', () => {
|
||||
const first = selectors.selectById(state, AClockworkOrange.id)
|
||||
expect(first).toBe(AClockworkOrange)
|
||||
const second = selectors.selectById(state, AnimalFarm.id)
|
||||
expect(second).toBe(AnimalFarm)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Uncomposed Selectors', () => {
|
||||
type State = EntityState<BookModel>
|
||||
|
||||
let adapter: EntityAdapter<BookModel>
|
||||
let selectors: EntitySelectors<BookModel, EntityState<BookModel>>
|
||||
let state: State
|
||||
|
||||
beforeEach(() => {
|
||||
adapter = createEntityAdapter({
|
||||
selectId: (book: BookModel) => book.id,
|
||||
})
|
||||
|
||||
state = adapter.setAll(adapter.getInitialState(), [
|
||||
AClockworkOrange,
|
||||
AnimalFarm,
|
||||
TheGreatGatsby,
|
||||
])
|
||||
|
||||
selectors = adapter.getSelectors()
|
||||
})
|
||||
|
||||
it('should create a selector for selecting the ids', () => {
|
||||
const ids = selectors.selectIds(state)
|
||||
|
||||
expect(ids).toEqual(state.ids)
|
||||
})
|
||||
|
||||
it('should create a selector for selecting the entities', () => {
|
||||
const entities = selectors.selectEntities(state)
|
||||
|
||||
expect(entities).toEqual(state.entities)
|
||||
})
|
||||
|
||||
it('should type single entity from Dictionary as entity type or undefined', () => {
|
||||
expectType<Selector<EntityState<BookModel>, BookModel | undefined>>(
|
||||
createSelector(selectors.selectEntities, (entities) => entities[0])
|
||||
)
|
||||
})
|
||||
|
||||
it('should create a selector for selecting the list of models', () => {
|
||||
const models = selectors.selectAll(state)
|
||||
|
||||
expect(models).toEqual([AClockworkOrange, AnimalFarm, TheGreatGatsby])
|
||||
})
|
||||
|
||||
it('should create a selector for selecting the count of models', () => {
|
||||
const total = selectors.selectTotal(state)
|
||||
|
||||
expect(total).toEqual(3)
|
||||
})
|
||||
|
||||
it('should create a selector for selecting a single item by ID', () => {
|
||||
const first = selectors.selectById(state, AClockworkOrange.id)
|
||||
expect(first).toBe(AClockworkOrange)
|
||||
const second = selectors.selectById(state, AnimalFarm.id)
|
||||
expect(second).toBe(AnimalFarm)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function expectType<T>(t: T) {
|
||||
return t
|
||||
}
|
||||
777
node_modules/@reduxjs/toolkit/src/entities/tests/unsorted_state_adapter.test.ts
generated
vendored
Normal file
777
node_modules/@reduxjs/toolkit/src/entities/tests/unsorted_state_adapter.test.ts
generated
vendored
Normal file
@@ -0,0 +1,777 @@
|
||||
import type { EntityAdapter, EntityState } from '../models'
|
||||
import { createEntityAdapter } from '../create_adapter'
|
||||
import type { BookModel } from './fixtures/book'
|
||||
import {
|
||||
TheGreatGatsby,
|
||||
AClockworkOrange,
|
||||
AnimalFarm,
|
||||
TheHobbit,
|
||||
} from './fixtures/book'
|
||||
import { createNextState } from '../..'
|
||||
|
||||
describe('Unsorted State Adapter', () => {
|
||||
let adapter: EntityAdapter<BookModel>
|
||||
let state: EntityState<BookModel>
|
||||
|
||||
beforeAll(() => {
|
||||
//eslint-disable-next-line
|
||||
Object.defineProperty(Array.prototype, 'unwantedField', {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
value: 'This should not appear anywhere',
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
delete (Array.prototype as any).unwantedField
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
adapter = createEntityAdapter({
|
||||
selectId: (book: BookModel) => book.id,
|
||||
})
|
||||
|
||||
state = { ids: [], entities: {} }
|
||||
})
|
||||
|
||||
it('should let you add one entity to the state', () => {
|
||||
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
|
||||
|
||||
expect(withOneEntity).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: TheGreatGatsby,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should not change state if you attempt to re-add an entity', () => {
|
||||
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
|
||||
|
||||
const readded = adapter.addOne(withOneEntity, TheGreatGatsby)
|
||||
|
||||
expect(readded).toBe(withOneEntity)
|
||||
})
|
||||
|
||||
it('should let you add many entities to the state', () => {
|
||||
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
|
||||
|
||||
const withManyMore = adapter.addMany(withOneEntity, [
|
||||
AClockworkOrange,
|
||||
AnimalFarm,
|
||||
])
|
||||
|
||||
expect(withManyMore).toEqual({
|
||||
ids: [TheGreatGatsby.id, AClockworkOrange.id, AnimalFarm.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: TheGreatGatsby,
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
[AnimalFarm.id]: AnimalFarm,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
const withAll = adapter.setAll(withOneEntity, [
|
||||
AClockworkOrange,
|
||||
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 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)
|
||||
|
||||
const withoutOne = adapter.removeOne(withOneEntity, TheGreatGatsby.id)
|
||||
|
||||
expect(withoutOne).toEqual({
|
||||
ids: [],
|
||||
entities: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you remove many entities by id from the state', () => {
|
||||
const withAll = adapter.setAll(state, [
|
||||
TheGreatGatsby,
|
||||
AClockworkOrange,
|
||||
AnimalFarm,
|
||||
])
|
||||
|
||||
const withoutMany = adapter.removeMany(withAll, [
|
||||
TheGreatGatsby.id,
|
||||
AClockworkOrange.id,
|
||||
])
|
||||
|
||||
expect(withoutMany).toEqual({
|
||||
ids: [AnimalFarm.id],
|
||||
entities: {
|
||||
[AnimalFarm.id]: AnimalFarm,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you remove all entities from the state', () => {
|
||||
const withAll = adapter.setAll(state, [
|
||||
TheGreatGatsby,
|
||||
AClockworkOrange,
|
||||
AnimalFarm,
|
||||
])
|
||||
|
||||
const withoutAll = adapter.removeAll(withAll)
|
||||
|
||||
expect(withoutAll).toEqual({
|
||||
ids: [],
|
||||
entities: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you update an entity in the state', () => {
|
||||
const withOne = adapter.addOne(state, TheGreatGatsby)
|
||||
const changes = { title: 'A New Hope' }
|
||||
|
||||
const withUpdates = adapter.updateOne(withOne, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes,
|
||||
})
|
||||
|
||||
expect(withUpdates).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: {
|
||||
...TheGreatGatsby,
|
||||
...changes,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should not change state if you attempt to update an entity that has not been added', () => {
|
||||
const withUpdates = adapter.updateOne(state, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes: { title: 'A New Title' },
|
||||
})
|
||||
|
||||
expect(withUpdates).toBe(state)
|
||||
})
|
||||
|
||||
it('should not change ids state if you attempt to update an entity that has already been added', () => {
|
||||
const withOne = adapter.addOne(state, TheGreatGatsby)
|
||||
const changes = { title: 'A New Hope' }
|
||||
|
||||
const withUpdates = adapter.updateOne(withOne, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes,
|
||||
})
|
||||
|
||||
expect(withOne.ids).toBe(withUpdates.ids)
|
||||
})
|
||||
|
||||
it('should let you update the id of entity', () => {
|
||||
const withOne = adapter.addOne(state, TheGreatGatsby)
|
||||
const changes = { id: 'A New Id' }
|
||||
|
||||
const withUpdates = adapter.updateOne(withOne, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes,
|
||||
})
|
||||
|
||||
expect(withUpdates).toEqual({
|
||||
ids: [changes.id],
|
||||
entities: {
|
||||
[changes.id]: {
|
||||
...TheGreatGatsby,
|
||||
...changes,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you update many entities by id in the state', () => {
|
||||
const firstChange = { title: 'First Change' }
|
||||
const secondChange = { title: 'Second Change' }
|
||||
const withMany = adapter.setAll(state, [TheGreatGatsby, AClockworkOrange])
|
||||
|
||||
const withUpdates = adapter.updateMany(withMany, [
|
||||
{ id: TheGreatGatsby.id, changes: firstChange },
|
||||
{ id: AClockworkOrange.id, changes: secondChange },
|
||||
])
|
||||
|
||||
expect(withUpdates).toEqual({
|
||||
ids: [TheGreatGatsby.id, AClockworkOrange.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: {
|
||||
...TheGreatGatsby,
|
||||
...firstChange,
|
||||
},
|
||||
[AClockworkOrange.id]: {
|
||||
...AClockworkOrange,
|
||||
...secondChange,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("doesn't break when multiple renames of one item occur", () => {
|
||||
const withA = adapter.addOne(state, { id: 'a', title: 'First' })
|
||||
|
||||
const withUpdates = adapter.updateMany(withA, [
|
||||
{ id: 'a', changes: { id: 'b' } },
|
||||
{ id: 'a', changes: { id: 'c' } },
|
||||
])
|
||||
|
||||
const { ids, entities } = withUpdates
|
||||
|
||||
/*
|
||||
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' } }
|
||||
}
|
||||
*/
|
||||
expect(ids.length).toBe(1)
|
||||
expect(ids).toEqual(['c'])
|
||||
expect(entities.a).toBeFalsy()
|
||||
expect(entities.b).toBeFalsy()
|
||||
expect(entities.c).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should let you add one entity to the state with upsert()', () => {
|
||||
const withOneEntity = adapter.upsertOne(state, TheGreatGatsby)
|
||||
expect(withOneEntity).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: TheGreatGatsby,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you update an entity in the state with upsert()', () => {
|
||||
const withOne = adapter.addOne(state, TheGreatGatsby)
|
||||
const changes = { title: 'A New Hope' }
|
||||
|
||||
const withUpdates = adapter.upsertOne(withOne, {
|
||||
...TheGreatGatsby,
|
||||
...changes,
|
||||
})
|
||||
expect(withUpdates).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: {
|
||||
...TheGreatGatsby,
|
||||
...changes,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you upsert many entities in the state', () => {
|
||||
const firstChange = { title: 'First Change' }
|
||||
const withMany = adapter.setAll(state, [TheGreatGatsby])
|
||||
|
||||
const withUpserts = adapter.upsertMany(withMany, [
|
||||
{ ...TheGreatGatsby, ...firstChange },
|
||||
AClockworkOrange,
|
||||
])
|
||||
|
||||
expect(withUpserts).toEqual({
|
||||
ids: [TheGreatGatsby.id, AClockworkOrange.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: {
|
||||
...TheGreatGatsby,
|
||||
...firstChange,
|
||||
},
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you add a new entity in the state with setOne()', () => {
|
||||
const withOne = adapter.setOne(state, TheGreatGatsby)
|
||||
expect(withOne).toEqual({
|
||||
ids: [TheGreatGatsby.id],
|
||||
entities: {
|
||||
[TheGreatGatsby.id]: TheGreatGatsby,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you replace an entity in the state with setOne()', () => {
|
||||
let withOne = adapter.setOne(state, TheHobbit)
|
||||
const changeWithoutAuthor = { id: TheHobbit.id, title: 'Silmarillion' }
|
||||
withOne = adapter.setOne(withOne, changeWithoutAuthor)
|
||||
|
||||
expect(withOne).toEqual({
|
||||
ids: [TheHobbit.id],
|
||||
entities: {
|
||||
[TheHobbit.id]: changeWithoutAuthor,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you set many entities in the state', () => {
|
||||
const changeWithoutAuthor = { id: TheHobbit.id, title: 'Silmarillion' }
|
||||
const withMany = adapter.setAll(state, [TheHobbit])
|
||||
|
||||
const withSetMany = adapter.setMany(withMany, [
|
||||
changeWithoutAuthor,
|
||||
AClockworkOrange,
|
||||
])
|
||||
|
||||
expect(withSetMany).toEqual({
|
||||
ids: [TheHobbit.id, AClockworkOrange.id],
|
||||
entities: {
|
||||
[TheHobbit.id]: changeWithoutAuthor,
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should let you set many entities in the state when passing in a dictionary', () => {
|
||||
const changeWithoutAuthor = { id: TheHobbit.id, title: 'Silmarillion' }
|
||||
const withMany = adapter.setAll(state, [TheHobbit])
|
||||
|
||||
const withSetMany = adapter.setMany(withMany, {
|
||||
[TheHobbit.id]: changeWithoutAuthor,
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
})
|
||||
|
||||
expect(withSetMany).toEqual({
|
||||
ids: [TheHobbit.id, AClockworkOrange.id],
|
||||
entities: {
|
||||
[TheHobbit.id]: changeWithoutAuthor,
|
||||
[AClockworkOrange.id]: AClockworkOrange,
|
||||
},
|
||||
})
|
||||
})
|
||||
it("only returns one entry for that id in the id's array", () => {
|
||||
const book1: BookModel = { id: 'a', title: 'First' }
|
||||
const book2: BookModel = { id: 'b', title: 'Second' }
|
||||
const initialState = adapter.getInitialState()
|
||||
const withItems = adapter.addMany(initialState, [book1, book2])
|
||||
|
||||
expect(withItems.ids).toEqual(['a', 'b'])
|
||||
const withUpdate = adapter.updateOne(withItems, {
|
||||
id: 'a',
|
||||
changes: { id: 'b' },
|
||||
})
|
||||
|
||||
expect(withUpdate.ids).toEqual(['b'])
|
||||
expect(withUpdate.entities['b']!.title).toBe(book1.title)
|
||||
})
|
||||
|
||||
describe('can be used mutably when wrapped in createNextState', () => {
|
||||
test('removeAll', () => {
|
||||
const withTwo = adapter.addMany(state, [TheGreatGatsby, AnimalFarm])
|
||||
const result = createNextState(withTwo, (draft) => {
|
||||
adapter.removeAll(draft)
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {},
|
||||
"ids": Array [],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('addOne', () => {
|
||||
const result = createNextState(state, (draft) => {
|
||||
adapter.addOne(draft, TheGreatGatsby)
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "The Great Gatsby",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('addMany', () => {
|
||||
const result = createNextState(state, (draft) => {
|
||||
adapter.addMany(draft, [TheGreatGatsby, AnimalFarm])
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"af": Object {
|
||||
"id": "af",
|
||||
"title": "Animal Farm",
|
||||
},
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "The Great Gatsby",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
"af",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('setAll', () => {
|
||||
const result = createNextState(state, (draft) => {
|
||||
adapter.setAll(draft, [TheGreatGatsby, AnimalFarm])
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"af": Object {
|
||||
"id": "af",
|
||||
"title": "Animal Farm",
|
||||
},
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "The Great Gatsby",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
"af",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('updateOne', () => {
|
||||
const withOne = adapter.addOne(state, TheGreatGatsby)
|
||||
const changes = { title: 'A New Hope' }
|
||||
const result = createNextState(withOne, (draft) => {
|
||||
adapter.updateOne(draft, {
|
||||
id: TheGreatGatsby.id,
|
||||
changes,
|
||||
})
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "A New Hope",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('updateMany', () => {
|
||||
const firstChange = { title: 'First Change' }
|
||||
const secondChange = { title: 'Second Change' }
|
||||
const thirdChange = { title: 'Third Change' }
|
||||
const fourthChange = { author: 'Fourth Change' }
|
||||
const withMany = adapter.setAll(state, [
|
||||
TheGreatGatsby,
|
||||
AClockworkOrange,
|
||||
TheHobbit,
|
||||
])
|
||||
|
||||
const result = createNextState(withMany, (draft) => {
|
||||
adapter.updateMany(draft, [
|
||||
{ id: TheHobbit.id, changes: firstChange },
|
||||
{ id: TheGreatGatsby.id, changes: secondChange },
|
||||
{ id: AClockworkOrange.id, changes: thirdChange },
|
||||
{ id: TheHobbit.id, changes: fourthChange },
|
||||
])
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"aco": Object {
|
||||
"id": "aco",
|
||||
"title": "Third Change",
|
||||
},
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "Second Change",
|
||||
},
|
||||
"th": Object {
|
||||
"author": "Fourth Change",
|
||||
"id": "th",
|
||||
"title": "First Change",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
"aco",
|
||||
"th",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('upsertOne (insert)', () => {
|
||||
const result = createNextState(state, (draft) => {
|
||||
adapter.upsertOne(draft, TheGreatGatsby)
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "The Great Gatsby",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('upsertOne (update)', () => {
|
||||
const withOne = adapter.upsertOne(state, TheGreatGatsby)
|
||||
const result = createNextState(withOne, (draft) => {
|
||||
adapter.upsertOne(draft, {
|
||||
id: TheGreatGatsby.id,
|
||||
title: 'A New Hope',
|
||||
})
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "A New Hope",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('upsertMany', () => {
|
||||
const withOne = adapter.upsertOne(state, TheGreatGatsby)
|
||||
const result = createNextState(withOne, (draft) => {
|
||||
adapter.upsertMany(draft, [
|
||||
{
|
||||
id: TheGreatGatsby.id,
|
||||
title: 'A New Hope',
|
||||
},
|
||||
AnimalFarm,
|
||||
])
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"af": Object {
|
||||
"id": "af",
|
||||
"title": "Animal Farm",
|
||||
},
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "A New Hope",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
"af",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('setOne (insert)', () => {
|
||||
const result = createNextState(state, (draft) => {
|
||||
adapter.setOne(draft, TheGreatGatsby)
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"tgg": Object {
|
||||
"id": "tgg",
|
||||
"title": "The Great Gatsby",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"tgg",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('setOne (update)', () => {
|
||||
const withOne = adapter.setOne(state, TheHobbit)
|
||||
const result = createNextState(withOne, (draft) => {
|
||||
adapter.setOne(draft, {
|
||||
id: TheHobbit.id,
|
||||
title: 'Silmarillion',
|
||||
})
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"th": Object {
|
||||
"id": "th",
|
||||
"title": "Silmarillion",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"th",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('setMany', () => {
|
||||
const withOne = adapter.setOne(state, TheHobbit)
|
||||
const result = createNextState(withOne, (draft) => {
|
||||
adapter.setMany(draft, [
|
||||
{
|
||||
id: TheHobbit.id,
|
||||
title: 'Silmarillion',
|
||||
},
|
||||
AnimalFarm,
|
||||
])
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"af": Object {
|
||||
"id": "af",
|
||||
"title": "Animal Farm",
|
||||
},
|
||||
"th": Object {
|
||||
"id": "th",
|
||||
"title": "Silmarillion",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"th",
|
||||
"af",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('removeOne', () => {
|
||||
const withTwo = adapter.addMany(state, [TheGreatGatsby, AnimalFarm])
|
||||
const result = createNextState(withTwo, (draft) => {
|
||||
adapter.removeOne(draft, TheGreatGatsby.id)
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"af": Object {
|
||||
"id": "af",
|
||||
"title": "Animal Farm",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"af",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('removeMany', () => {
|
||||
const withThree = adapter.addMany(state, [
|
||||
TheGreatGatsby,
|
||||
AnimalFarm,
|
||||
AClockworkOrange,
|
||||
])
|
||||
const result = createNextState(withThree, (draft) => {
|
||||
adapter.removeMany(draft, [TheGreatGatsby.id, AnimalFarm.id])
|
||||
})
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"entities": Object {
|
||||
"aco": Object {
|
||||
"id": "aco",
|
||||
"title": "A Clockwork Orange",
|
||||
},
|
||||
},
|
||||
"ids": Array [
|
||||
"aco",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
65
node_modules/@reduxjs/toolkit/src/entities/tests/utils.spec.ts
generated
vendored
Normal file
65
node_modules/@reduxjs/toolkit/src/entities/tests/utils.spec.ts
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import { AClockworkOrange } from './fixtures/book'
|
||||
|
||||
describe('Entity utils', () => {
|
||||
describe(`selectIdValue()`, () => {
|
||||
const OLD_ENV = process.env
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules() // this is important - it clears the cache
|
||||
process.env = { ...OLD_ENV, NODE_ENV: 'development' }
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
process.env = OLD_ENV
|
||||
jest.resetAllMocks()
|
||||
})
|
||||
|
||||
it('should not warn when key does exist', () => {
|
||||
const { selectIdValue } = require('../utils')
|
||||
const spy = jest.spyOn(console, 'warn')
|
||||
|
||||
selectIdValue(AClockworkOrange, (book: any) => book.id)
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should warn when key does not exist in dev mode', () => {
|
||||
const { selectIdValue } = require('../utils')
|
||||
const spy = jest.spyOn(console, 'warn')
|
||||
|
||||
selectIdValue(AClockworkOrange, (book: any) => book.foo)
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should warn when key is undefined in dev mode', () => {
|
||||
const { selectIdValue } = require('../utils')
|
||||
const spy = jest.spyOn(console, 'warn')
|
||||
|
||||
const undefinedAClockworkOrange = { ...AClockworkOrange, id: undefined }
|
||||
selectIdValue(undefinedAClockworkOrange, (book: any) => book.id)
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not warn when key does not exist in prod mode', () => {
|
||||
process.env.NODE_ENV = 'production'
|
||||
const { selectIdValue } = require('../utils')
|
||||
const spy = jest.spyOn(console, 'warn')
|
||||
|
||||
selectIdValue(AClockworkOrange, (book: any) => book.foo)
|
||||
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not warn when key is undefined in prod mode', () => {
|
||||
process.env.NODE_ENV = 'production'
|
||||
const { selectIdValue } = require('../utils')
|
||||
const spy = jest.spyOn(console, 'warn')
|
||||
|
||||
const undefinedAClockworkOrange = { ...AClockworkOrange, id: undefined }
|
||||
selectIdValue(undefinedAClockworkOrange, (book: any) => book.id)
|
||||
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
198
node_modules/@reduxjs/toolkit/src/entities/unsorted_state_adapter.ts
generated
vendored
Normal file
198
node_modules/@reduxjs/toolkit/src/entities/unsorted_state_adapter.ts
generated
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
import type {
|
||||
EntityState,
|
||||
EntityStateAdapter,
|
||||
IdSelector,
|
||||
Update,
|
||||
EntityId,
|
||||
} from './models'
|
||||
import {
|
||||
createStateOperator,
|
||||
createSingleArgumentStateOperator,
|
||||
} from './state_adapter'
|
||||
import {
|
||||
selectIdValue,
|
||||
ensureEntitiesArray,
|
||||
splitAddedUpdatedEntities,
|
||||
} from './utils'
|
||||
|
||||
export function createUnsortedStateAdapter<T>(
|
||||
selectId: IdSelector<T>
|
||||
): EntityStateAdapter<T> {
|
||||
type R = EntityState<T>
|
||||
|
||||
function addOneMutably(entity: T, state: R): void {
|
||||
const key = selectIdValue(entity, selectId)
|
||||
|
||||
if (key in state.entities) {
|
||||
return
|
||||
}
|
||||
|
||||
state.ids.push(key)
|
||||
state.entities[key] = entity
|
||||
}
|
||||
|
||||
function addManyMutably(
|
||||
newEntities: readonly T[] | Record<EntityId, T>,
|
||||
state: R
|
||||
): void {
|
||||
newEntities = ensureEntitiesArray(newEntities)
|
||||
|
||||
for (const entity of newEntities) {
|
||||
addOneMutably(entity, state)
|
||||
}
|
||||
}
|
||||
|
||||
function setOneMutably(entity: T, state: R): void {
|
||||
const key = selectIdValue(entity, selectId)
|
||||
if (!(key in state.entities)) {
|
||||
state.ids.push(key)
|
||||
}
|
||||
state.entities[key] = entity
|
||||
}
|
||||
|
||||
function setManyMutably(
|
||||
newEntities: readonly T[] | Record<EntityId, T>,
|
||||
state: R
|
||||
): void {
|
||||
newEntities = ensureEntitiesArray(newEntities)
|
||||
for (const entity of newEntities) {
|
||||
setOneMutably(entity, state)
|
||||
}
|
||||
}
|
||||
|
||||
function setAllMutably(
|
||||
newEntities: readonly T[] | Record<EntityId, T>,
|
||||
state: R
|
||||
): void {
|
||||
newEntities = ensureEntitiesArray(newEntities)
|
||||
|
||||
state.ids = []
|
||||
state.entities = {}
|
||||
|
||||
addManyMutably(newEntities, state)
|
||||
}
|
||||
|
||||
function removeOneMutably(key: EntityId, state: R): void {
|
||||
return removeManyMutably([key], state)
|
||||
}
|
||||
|
||||
function removeManyMutably(keys: readonly EntityId[], state: R): void {
|
||||
let didMutate = false
|
||||
|
||||
keys.forEach((key) => {
|
||||
if (key in state.entities) {
|
||||
delete state.entities[key]
|
||||
didMutate = true
|
||||
}
|
||||
})
|
||||
|
||||
if (didMutate) {
|
||||
state.ids = state.ids.filter((id) => id in state.entities)
|
||||
}
|
||||
}
|
||||
|
||||
function removeAllMutably(state: R): void {
|
||||
Object.assign(state, {
|
||||
ids: [],
|
||||
entities: {},
|
||||
})
|
||||
}
|
||||
|
||||
function takeNewKey(
|
||||
keys: { [id: string]: EntityId },
|
||||
update: Update<T>,
|
||||
state: R
|
||||
): boolean {
|
||||
const original = state.entities[update.id]
|
||||
const updated: T = Object.assign({}, original, update.changes)
|
||||
const newKey = selectIdValue(updated, selectId)
|
||||
const hasNewKey = newKey !== update.id
|
||||
|
||||
if (hasNewKey) {
|
||||
keys[update.id] = newKey
|
||||
delete state.entities[update.id]
|
||||
}
|
||||
|
||||
state.entities[newKey] = updated
|
||||
|
||||
return hasNewKey
|
||||
}
|
||||
|
||||
function updateOneMutably(update: Update<T>, state: R): void {
|
||||
return updateManyMutably([update], state)
|
||||
}
|
||||
|
||||
function updateManyMutably(
|
||||
updates: ReadonlyArray<Update<T>>,
|
||||
state: R
|
||||
): void {
|
||||
const newKeys: { [id: string]: EntityId } = {}
|
||||
|
||||
const updatesPerEntity: { [id: string]: Update<T> } = {}
|
||||
|
||||
updates.forEach((update) => {
|
||||
// Only apply updates to entities that currently exist
|
||||
if (update.id in state.entities) {
|
||||
// If there are multiple updates to one entity, merge them together
|
||||
updatesPerEntity[update.id] = {
|
||||
id: update.id,
|
||||
// Spreads ignore falsy values, so this works even if there isn't
|
||||
// an existing update already at this key
|
||||
changes: {
|
||||
...(updatesPerEntity[update.id]
|
||||
? updatesPerEntity[update.id].changes
|
||||
: null),
|
||||
...update.changes,
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
updates = Object.values(updatesPerEntity)
|
||||
|
||||
const didMutateEntities = updates.length > 0
|
||||
|
||||
if (didMutateEntities) {
|
||||
const didMutateIds =
|
||||
updates.filter((update) => takeNewKey(newKeys, update, state)).length >
|
||||
0
|
||||
|
||||
if (didMutateIds) {
|
||||
state.ids = Object.keys(state.entities)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function upsertOneMutably(entity: T, state: R): void {
|
||||
return upsertManyMutably([entity], state)
|
||||
}
|
||||
|
||||
function upsertManyMutably(
|
||||
newEntities: readonly T[] | Record<EntityId, T>,
|
||||
state: R
|
||||
): void {
|
||||
const [added, updated] = splitAddedUpdatedEntities<T>(
|
||||
newEntities,
|
||||
selectId,
|
||||
state
|
||||
)
|
||||
|
||||
updateManyMutably(updated, state)
|
||||
addManyMutably(added, state)
|
||||
}
|
||||
|
||||
return {
|
||||
removeAll: createSingleArgumentStateOperator(removeAllMutably),
|
||||
addOne: createStateOperator(addOneMutably),
|
||||
addMany: createStateOperator(addManyMutably),
|
||||
setOne: createStateOperator(setOneMutably),
|
||||
setMany: createStateOperator(setManyMutably),
|
||||
setAll: createStateOperator(setAllMutably),
|
||||
updateOne: createStateOperator(updateOneMutably),
|
||||
updateMany: createStateOperator(updateManyMutably),
|
||||
upsertOne: createStateOperator(upsertOneMutably),
|
||||
upsertMany: createStateOperator(upsertManyMutably),
|
||||
removeOne: createStateOperator(removeOneMutably),
|
||||
removeMany: createStateOperator(removeManyMutably),
|
||||
}
|
||||
}
|
||||
49
node_modules/@reduxjs/toolkit/src/entities/utils.ts
generated
vendored
Normal file
49
node_modules/@reduxjs/toolkit/src/entities/utils.ts
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { EntityState, IdSelector, Update, EntityId } from './models'
|
||||
|
||||
export function selectIdValue<T>(entity: T, selectId: IdSelector<T>) {
|
||||
const key = selectId(entity)
|
||||
|
||||
if (process.env.NODE_ENV !== 'production' && key === undefined) {
|
||||
console.warn(
|
||||
'The entity passed to the `selectId` implementation returned undefined.',
|
||||
'You should probably provide your own `selectId` implementation.',
|
||||
'The entity that was passed:',
|
||||
entity,
|
||||
'The `selectId` implementation:',
|
||||
selectId.toString()
|
||||
)
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
export function ensureEntitiesArray<T>(
|
||||
entities: readonly T[] | Record<EntityId, T>
|
||||
): readonly T[] {
|
||||
if (!Array.isArray(entities)) {
|
||||
entities = Object.values(entities)
|
||||
}
|
||||
|
||||
return entities
|
||||
}
|
||||
|
||||
export function splitAddedUpdatedEntities<T>(
|
||||
newEntities: readonly T[] | Record<EntityId, T>,
|
||||
selectId: IdSelector<T>,
|
||||
state: EntityState<T>
|
||||
): [T[], Update<T>[]] {
|
||||
newEntities = ensureEntitiesArray(newEntities)
|
||||
|
||||
const added: T[] = []
|
||||
const updated: Update<T>[] = []
|
||||
|
||||
for (const entity of newEntities) {
|
||||
const id = selectIdValue(entity, selectId)
|
||||
if (id in state.entities) {
|
||||
updated.push({ id, changes: entity })
|
||||
} else {
|
||||
added.push(entity)
|
||||
}
|
||||
}
|
||||
return [added, updated]
|
||||
}
|
||||
Reference in New Issue
Block a user