Skip to content

Commit 92fd7c3

Browse files
feat(signals): add upsert entity updaters (#4727)
1 parent 40e78d9 commit 92fd7c3

File tree

9 files changed

+617
-34
lines changed

9 files changed

+617
-34
lines changed

modules/signals/entities/spec/mocks.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
export type User = { id: number; firstName: string; lastName: string };
1+
export type User = {
2+
id: number;
3+
firstName: string;
4+
lastName: string;
5+
age?: number;
6+
};
27
export type Todo = { _id: string; text: string; completed: boolean };
38

49
export const user1: User = { id: 1, firstName: 'John', lastName: 'Doe' };

modules/signals/entities/spec/updaters/prepend-entity.spec.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { patchState, signalStore, type } from '@ngrx/signals';
2-
import {
3-
prependEntity,
4-
entityConfig,
5-
withEntities,
6-
} from '../../src';
2+
import { prependEntity, entityConfig, withEntities } from '../../src';
73
import { Todo, todo1, todo2, User, user1, user2 } from '../mocks';
84
import { selectTodoId as selectId } from '../helpers';
95

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import { patchState, signalStore, type } from '@ngrx/signals';
2+
import { entityConfig, upsertEntities, withEntities } from '../../src';
3+
import { Todo, todo1, todo2, todo3, User, user1, user2, user3 } from '../mocks';
4+
import { selectTodoId as selectId } from '../helpers';
5+
6+
const user1WithAge: User = {
7+
...user1,
8+
age: 40,
9+
};
10+
const newUser1WithoutAge: User = {
11+
...user2,
12+
id: user1WithAge.id,
13+
};
14+
const expectedUser1: User = {
15+
...user2,
16+
id: user1WithAge.id,
17+
age: user1WithAge.age,
18+
};
19+
20+
describe('upsertEntities', () => {
21+
it('adds entities if they do not exist', () => {
22+
const Store = signalStore({ protectedState: false }, withEntities<User>());
23+
const store = new Store();
24+
25+
patchState(store, upsertEntities([user1]));
26+
27+
expect(store.entityMap()).toEqual({ 1: user1 });
28+
expect(store.ids()).toEqual([1]);
29+
expect(store.entities()).toEqual([user1]);
30+
31+
patchState(store, upsertEntities([user2, user3]));
32+
33+
expect(store.entityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });
34+
expect(store.ids()).toEqual([1, 2, 3]);
35+
expect(store.entities()).toEqual([user1, user2, user3]);
36+
});
37+
38+
it('updates entities if they already exist', () => {
39+
const Store = signalStore({ protectedState: false }, withEntities<User>());
40+
const store = new Store();
41+
42+
patchState(
43+
store,
44+
upsertEntities([user1WithAge, user2, { ...user2, lastName: 'Hendrix' }])
45+
);
46+
47+
expect(store.entityMap()).toEqual({
48+
1: user1WithAge,
49+
2: { ...user2, lastName: 'Hendrix' },
50+
});
51+
expect(store.ids()).toEqual([1, 2]);
52+
expect(store.entities()).toEqual([
53+
user1WithAge,
54+
{ ...user2, lastName: 'Hendrix' },
55+
]);
56+
57+
patchState(
58+
store,
59+
upsertEntities([user3, newUser1WithoutAge]),
60+
upsertEntities([] as User[])
61+
);
62+
63+
expect(store.entityMap()).toEqual({
64+
1: expectedUser1,
65+
2: { ...user2, lastName: 'Hendrix' },
66+
3: user3,
67+
});
68+
expect(store.ids()).toEqual([1, 2, 3]);
69+
expect(store.entities()).toEqual([
70+
expectedUser1,
71+
{ ...user2, lastName: 'Hendrix' },
72+
user3,
73+
]);
74+
});
75+
76+
it('adds entities to the specified collection if they do not exist', () => {
77+
const Store = signalStore(
78+
{ protectedState: false },
79+
withEntities({
80+
entity: type<User>(),
81+
collection: 'user',
82+
})
83+
);
84+
const store = new Store();
85+
86+
patchState(
87+
store,
88+
upsertEntities([user1, user2], { collection: 'user' }),
89+
upsertEntities([user3], { collection: 'user' })
90+
);
91+
92+
expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });
93+
expect(store.userIds()).toEqual([1, 2, 3]);
94+
expect(store.userEntities()).toEqual([user1, user2, user3]);
95+
});
96+
97+
it('updates entities to the specified collection if they already exist', () => {
98+
const Store = signalStore(
99+
{ protectedState: false },
100+
withEntities({
101+
entity: type<User>(),
102+
collection: 'user',
103+
})
104+
);
105+
const store = new Store();
106+
107+
patchState(
108+
store,
109+
upsertEntities([user1WithAge, user3, newUser1WithoutAge], {
110+
collection: 'user',
111+
})
112+
);
113+
114+
patchState(
115+
store,
116+
upsertEntities([] as User[], { collection: 'user' }),
117+
upsertEntities([user3, user2, { ...user3, firstName: 'Jimmy' }], {
118+
collection: 'user',
119+
})
120+
);
121+
122+
expect(store.userEntityMap()).toEqual({
123+
1: expectedUser1,
124+
3: { ...user3, firstName: 'Jimmy' },
125+
2: user2,
126+
});
127+
expect(store.userIds()).toEqual([1, 3, 2]);
128+
expect(store.userEntities()).toEqual([
129+
expectedUser1,
130+
{ ...user3, firstName: 'Jimmy' },
131+
user2,
132+
]);
133+
});
134+
135+
it('adds entities with a custom id if they do not exist', () => {
136+
const Store = signalStore({ protectedState: false }, withEntities<Todo>());
137+
const store = new Store();
138+
139+
patchState(store, upsertEntities([todo2, todo3], { selectId }));
140+
141+
expect(store.entityMap()).toEqual({ y: todo2, z: todo3 });
142+
expect(store.ids()).toEqual(['y', 'z']);
143+
expect(store.entities()).toEqual([todo2, todo3]);
144+
145+
patchState(
146+
store,
147+
upsertEntities([todo1], { selectId }),
148+
upsertEntities([] as Todo[], { selectId })
149+
);
150+
151+
expect(store.entityMap()).toEqual({ y: todo2, z: todo3, x: todo1 });
152+
expect(store.ids()).toEqual(['y', 'z', 'x']);
153+
expect(store.entities()).toEqual([todo2, todo3, todo1]);
154+
});
155+
156+
it('updates entities with a custom id if they already exist', () => {
157+
const Store = signalStore({ protectedState: false }, withEntities<Todo>());
158+
const store = new Store();
159+
160+
patchState(
161+
store,
162+
upsertEntities([todo1], { selectId }),
163+
upsertEntities([todo2, { ...todo1, text: 'Signals' }], { selectId }),
164+
upsertEntities([] as Todo[], { selectId })
165+
);
166+
167+
patchState(
168+
store,
169+
upsertEntities([] as Todo[], { selectId }),
170+
upsertEntities([todo3, todo2, { ...todo2, text: 'NgRx' }, todo1], {
171+
selectId,
172+
})
173+
);
174+
175+
expect(store.entityMap()).toEqual({
176+
x: todo1,
177+
y: { ...todo2, text: 'NgRx' },
178+
z: todo3,
179+
});
180+
expect(store.ids()).toEqual(['x', 'y', 'z']);
181+
expect(store.entities()).toEqual([
182+
todo1,
183+
{ ...todo2, text: 'NgRx' },
184+
todo3,
185+
]);
186+
});
187+
188+
it('adds entities with a custom id to the specified collection if they do not exist', () => {
189+
const Store = signalStore(
190+
{ protectedState: false },
191+
withEntities({
192+
entity: type<Todo>(),
193+
collection: 'todo',
194+
})
195+
);
196+
const store = new Store();
197+
198+
patchState(
199+
store,
200+
upsertEntities([todo3, todo2], {
201+
collection: 'todo',
202+
selectId,
203+
})
204+
);
205+
206+
expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2 });
207+
expect(store.todoIds()).toEqual(['z', 'y']);
208+
expect(store.todoEntities()).toEqual([todo3, todo2]);
209+
210+
patchState(
211+
store,
212+
upsertEntities([todo1], { collection: 'todo', selectId }),
213+
upsertEntities([] as Todo[], { collection: 'todo', selectId })
214+
);
215+
216+
expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2, x: todo1 });
217+
expect(store.todoIds()).toEqual(['z', 'y', 'x']);
218+
expect(store.todoEntities()).toEqual([todo3, todo2, todo1]);
219+
});
220+
221+
it('updates entities with a custom id to the specified collection if they already exist', () => {
222+
const todoConfig = entityConfig({
223+
entity: type<Todo>(),
224+
collection: 'todo',
225+
selectId,
226+
});
227+
228+
const Store = signalStore(
229+
{ protectedState: false },
230+
withEntities(todoConfig)
231+
);
232+
const store = new Store();
233+
234+
patchState(
235+
store,
236+
upsertEntities(
237+
[todo2, { ...todo2, text: 'NgRx' }, todo3, todo2],
238+
todoConfig
239+
),
240+
upsertEntities([] as Todo[], todoConfig),
241+
upsertEntities([todo3, { ...todo3, text: 'NgRx' }, todo1], todoConfig)
242+
);
243+
244+
expect(store.todoEntityMap()).toEqual({
245+
y: todo2,
246+
z: { ...todo3, text: 'NgRx' },
247+
x: todo1,
248+
});
249+
expect(store.todoIds()).toEqual(['y', 'z', 'x']);
250+
expect(store.todoEntities()).toEqual([
251+
todo2,
252+
{ ...todo3, text: 'NgRx' },
253+
todo1,
254+
]);
255+
});
256+
});

0 commit comments

Comments
 (0)