Skip to content

Commit

Permalink
feat(signals): add entities subpackage (#4090)
Browse files Browse the repository at this point in the history
  • Loading branch information
markostanimirovic authored Nov 8, 2023
1 parent 73fda59 commit f01bcd1
Show file tree
Hide file tree
Showing 35 changed files with 2,663 additions and 2 deletions.
13 changes: 12 additions & 1 deletion modules/signals/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@
},
"rules": {
"@angular-eslint/directive-selector": "off",
"@angular-eslint/component-selector": "off"
"@angular-eslint/component-selector": "off",
"@nx/enforce-module-boundaries": "off",
"@typescript-eslint/ban-types": [
"error",
{
"extendDefaults": true,
"types": {
"{}": false,
"Function": false
}
}
]
},
"plugins": ["@typescript-eslint"]
},
Expand Down
1 change: 1 addition & 0 deletions modules/signals/entities/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src/index';
5 changes: 5 additions & 0 deletions modules/signals/entities/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"lib": {
"entryFile": "index.ts"
}
}
10 changes: 10 additions & 0 deletions modules/signals/entities/spec/mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type User = { id: number; firstName: string; lastName: string };
export type Todo = { _id: string; text: string; completed: boolean };

export const user1: User = { id: 1, firstName: 'John', lastName: 'Doe' };
export const user2: User = { id: 2, firstName: 'Jane', lastName: 'Smith' };
export const user3: User = { id: 3, firstName: 'Joe', lastName: 'Johnson' };

export const todo1: Todo = { _id: 'x', text: 'Buy milk', completed: true };
export const todo2: Todo = { _id: 'y', text: 'Buy eggs', completed: false };
export const todo3: Todo = { _id: 'z', text: 'Make bread', completed: true };
248 changes: 248 additions & 0 deletions modules/signals/entities/spec/updaters/add-entities.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import { patchState, signalStore, type } from '@ngrx/signals';
import { addEntities, withEntities } from '../../src';
import { Todo, todo1, todo2, todo3, User, user1, user2, user3 } from '../mocks';

describe('addEntities', () => {
it('adds entities if they do not exist', () => {
const Store = signalStore(withEntities<User>());
const store = new Store();

patchState(store, addEntities([user1]));

expect(store.entityMap()).toEqual({ 1: user1 });
expect(store.ids()).toEqual([1]);
expect(store.entities()).toEqual([user1]);

patchState(store, addEntities([user2, user3]));

expect(store.entityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });
expect(store.ids()).toEqual([1, 2, 3]);
expect(store.entities()).toEqual([user1, user2, user3]);
});

it('does not add entities if they already exist', () => {
const Store = signalStore(withEntities<User>());
const store = new Store();

patchState(store, addEntities([user1, user2]));

const entityMap = store.entityMap();
const ids = store.ids();
const entities = store.entities();

patchState(
store,
addEntities([user2, { ...user2, firstName: 'Jack' }, user1]),
addEntities([] as User[])
);

expect(store.entityMap()).toBe(entityMap);
expect(store.ids()).toBe(ids);
expect(store.entities()).toBe(entities);

expect(store.entityMap()).toEqual({ 1: user1, 2: user2 });
expect(store.ids()).toEqual([1, 2]);
expect(store.entities()).toEqual([user1, user2]);

patchState(store, addEntities([user1, user3, user2]));

expect(store.entityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });
expect(store.ids()).toEqual([1, 2, 3]);
expect(store.entities()).toEqual([user1, user2, user3]);
});

it('adds entities to the specified collection if they do not exist', () => {
const Store = signalStore(
withEntities({
entity: type<User>(),
collection: 'user',
})
);
const store = new Store();

patchState(
store,
addEntities([user1, user2], { collection: 'user' }),
addEntities([user3], { collection: 'user' })
);

expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });
expect(store.userIds()).toEqual([1, 2, 3]);
expect(store.userEntities()).toEqual([user1, user2, user3]);
});

it('does not add entities to the specified collection if they already exist', () => {
const Store = signalStore(
withEntities({
entity: type<User>(),
collection: 'user',
})
);
const store = new Store();

patchState(
store,
addEntities([user1, { ...user1, lastName: 'Hendrix' }, user3, user1], {
collection: 'user',
})
);

const userEntityMap = store.userEntityMap();
const userIds = store.userIds();
const userEntities = store.userEntities();

patchState(
store,
addEntities([] as User[], { collection: 'user' }),
addEntities([user3, { ...user3, firstName: 'Jimmy' }, user1], {
collection: 'user',
})
);

expect(store.userEntityMap()).toBe(userEntityMap);
expect(store.userIds()).toBe(userIds);
expect(store.userEntities()).toBe(userEntities);
expect(store.userEntityMap()).toEqual({ 1: user1, 3: user3 });
expect(store.userIds()).toEqual([1, 3]);
expect(store.userEntities()).toEqual([user1, user3]);

patchState(
store,
addEntities([user1, user2, user3], { collection: 'user' })
);
expect(store.userEntityMap()).toEqual({ 1: user1, 3: user3, 2: user2 });
expect(store.userIds()).toEqual([1, 3, 2]);
expect(store.userEntities()).toEqual([user1, user3, user2]);
});

it('adds entities with the specified idKey if they do not exist', () => {
const Store = signalStore(withEntities<Todo>());
const store = new Store();

patchState(store, addEntities([todo2, todo3], { idKey: '_id' }));

expect(store.entityMap()).toEqual({ y: todo2, z: todo3 });
expect(store.ids()).toEqual(['y', 'z']);
expect(store.entities()).toEqual([todo2, todo3]);

patchState(
store,
addEntities([todo1], { idKey: '_id' }),
addEntities([] as Todo[], { idKey: '_id' })
);

expect(store.entityMap()).toEqual({ y: todo2, z: todo3, x: todo1 });
expect(store.ids()).toEqual(['y', 'z', 'x']);
expect(store.entities()).toEqual([todo2, todo3, todo1]);
});

it('does not add entities with the specified idKey if they already exist', () => {
const Store = signalStore(withEntities<Todo>());
const store = new Store();

patchState(
store,
addEntities([todo1], { idKey: '_id' }),
addEntities([todo2, todo1], { idKey: '_id' }),
addEntities([] as Todo[], { idKey: '_id' })
);

const entityMap = store.entityMap();
const ids = store.ids();
const entities = store.entities();

patchState(
store,
addEntities([] as Todo[], { idKey: '_id' }),
addEntities([todo2, { ...todo2, text: 'NgRx' }, todo1], { idKey: '_id' })
);

expect(store.entityMap()).toBe(entityMap);
expect(store.ids()).toBe(ids);
expect(store.entities()).toBe(entities);
expect(store.entityMap()).toEqual({ x: todo1, y: todo2 });
expect(store.ids()).toEqual(['x', 'y']);
expect(store.entities()).toEqual([todo1, todo2]);

patchState(store, addEntities([todo1, todo3, todo2], { idKey: '_id' }));

expect(store.entityMap()).toEqual({ x: todo1, y: todo2, z: todo3 });
expect(store.ids()).toEqual(['x', 'y', 'z']);
expect(store.entities()).toEqual([todo1, todo2, todo3]);
});

it('adds entities with the specified idKey to the specified collection if they do not exist', () => {
const Store = signalStore(
withEntities({
entity: type<Todo>(),
collection: 'todo',
})
);
const store = new Store();

patchState(
store,
addEntities([todo3, todo2], {
collection: 'todo',
idKey: '_id',
})
);

expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2 });
expect(store.todoIds()).toEqual(['z', 'y']);
expect(store.todoEntities()).toEqual([todo3, todo2]);

patchState(
store,
addEntities([todo1], { collection: 'todo', idKey: '_id' }),
addEntities([] as Todo[], { collection: 'todo', idKey: '_id' })
);

expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2, x: todo1 });
expect(store.todoIds()).toEqual(['z', 'y', 'x']);
expect(store.todoEntities()).toEqual([todo3, todo2, todo1]);
});

it('does not add entities with the specified idKey to the specified collection if they already exist', () => {
const todoMeta = {
entity: type<Todo>(),
collection: 'todo',
idKey: '_id',
} as const;

const Store = signalStore(withEntities(todoMeta));
const store = new Store();

patchState(
store,
addEntities([todo2, { ...todo2, text: 'NgRx' }, todo3, todo2], todoMeta)
);

const todoEntityMap = store.todoEntityMap();
const todoIds = store.todoIds();
const todoEntities = store.todoEntities();

patchState(
store,
addEntities([] as Todo[], todoMeta),
addEntities([todo3, todo2, { ...todo3, text: 'NgRx' }], todoMeta)
);

expect(store.todoEntityMap()).toBe(todoEntityMap);
expect(store.todoIds()).toBe(todoIds);
expect(store.todoEntities()).toBe(todoEntities);
expect(store.todoEntityMap()).toEqual({ y: todo2, z: todo3 });
expect(store.todoIds()).toEqual(['y', 'z']);
expect(store.todoEntities()).toEqual([todo2, todo3]);

patchState(
store,
addEntities([todo1, todo2, todo3], todoMeta),
addEntities([todo2, todo3, todo1], todoMeta)
);

expect(store.todoEntityMap()).toEqual({ y: todo2, z: todo3, x: todo1 });
expect(store.todoIds()).toEqual(['y', 'z', 'x']);
expect(store.todoEntities()).toEqual([todo2, todo3, todo1]);
});
});
Loading

0 comments on commit f01bcd1

Please sign in to comment.