Skip to content

Commit

Permalink
feat(Platform): Introduce @ngrx/entity (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeRyanDev authored and brandonroberts committed Jul 28, 2017
1 parent c490bc9 commit 9bdfd70
Show file tree
Hide file tree
Showing 22 changed files with 1,032 additions and 58 deletions.
4 changes: 4 additions & 0 deletions build/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ export const packages: PackageDescription[] = [
name: 'store-devtools',
hasTestingModule: false,
},
{
name: 'entity',
hasTestingModule: false,
},
];
6 changes: 6 additions & 0 deletions modules/entity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@ngrx/entity
=======

The sources for this package are in the main [ngrx/platform](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.

License: MIT
7 changes: 7 additions & 0 deletions modules/entity/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* DO NOT EDIT
*
* This file is automatically generated at build
*/

export * from './public_api';
22 changes: 22 additions & 0 deletions modules/entity/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@ngrx/entity",
"version": "4.0.1",
"description": "Common utilities for entity reducers",
"module": "@ngrx/entity.es5.js",
"es2015": "@ngrx/entity.js",
"main": "bundles/entity.umd.js",
"typings": "entity.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/ngrx/platform.git"
},
"authors": [
"Mike Ryan"
],
"license": "MIT",
"peerDependencies": {
"@angular/core": "^4.0.0",
"@ngrx/store": "^4.0.0",
"rxjs": "^5.0.0"
}
}
1 change: 1 addition & 0 deletions modules/entity/public_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src/index';
10 changes: 10 additions & 0 deletions modules/entity/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default {
entry: './dist/entity/@ngrx/entity.es5.js',
dest: './dist/entity/bundles/entity.umd.js',
format: 'umd',
exports: 'named',
moduleName: 'ngrx.entity',
globals: {
'@ngrx/store': 'ngrx.store'
}
}
33 changes: 33 additions & 0 deletions modules/entity/spec/entity_state.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createEntityAdapter, EntityAdapter } from '../src';
import { 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: {},
});
});
});
21 changes: 21 additions & 0 deletions modules/entity/spec/fixtures/book.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const deepFreeze = require('deep-freeze');

export interface BookModel {
id: string;
title: string;
}

export const AClockworkOrange: BookModel = deepFreeze({
id: 'aco',
title: 'A Clockwork Orange',
});

export const AnimalFarm: BookModel = deepFreeze({
id: 'af',
title: 'Animal Farm',
});

export const TheGreatGatsby: BookModel = deepFreeze({
id: 'tgg',
title: 'The Great Gatsby',
});
234 changes: 234 additions & 0 deletions modules/entity/spec/sorted_state_adapter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { EntityStateAdapter, EntityState } from '../src/models';
import { createEntityAdapter } from '../src/create_adapter';
import {
BookModel,
TheGreatGatsby,
AClockworkOrange,
AnimalFarm,
} from './fixtures/book';

describe('Sorted State Adapter', () => {
let adapter: EntityStateAdapter<BookModel>;
let state: EntityState<BookModel>;

beforeEach(() => {
adapter = createEntityAdapter({
selectId: (book: BookModel) => book.id,
sort: (a, b) => a.title.localeCompare(b.title),
});

state = { ids: [], entities: {} };
});

it('should let you add one entity to the state', () => {
const withOneEntity = adapter.addOne(TheGreatGatsby, state);

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(TheGreatGatsby, state);

const readded = adapter.addOne(TheGreatGatsby, withOneEntity);

expect(readded).toEqual(withOneEntity);
});

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

const withManyMore = adapter.addMany(
[AClockworkOrange, AnimalFarm],
withOneEntity
);

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 all entities to the state', () => {
const withOneEntity = adapter.addOne(TheGreatGatsby, state);

const withAll = adapter.addAll(
[AClockworkOrange, AnimalFarm],
withOneEntity
);

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(TheGreatGatsby, state);

const withoutOne = adapter.removeOne(TheGreatGatsby.id, state);

expect(withoutOne).toEqual({
ids: [],
entities: {},
});
});

it('should let you remove many entities from the state', () => {
const withAll = adapter.addAll(
[TheGreatGatsby, AClockworkOrange, AnimalFarm],
state
);

const withoutMany = adapter.removeMany(
[TheGreatGatsby.id, AClockworkOrange.id],
withAll
);

expect(withoutMany).toEqual({
ids: [AnimalFarm.id],
entities: {
[AnimalFarm.id]: AnimalFarm,
},
});
});

it('should let you remove all entities from the state', () => {
const withAll = adapter.addAll(
[TheGreatGatsby, AClockworkOrange, AnimalFarm],
state
);

const withoutAll = adapter.removeAll(withAll);

expect(withoutAll).toEqual({
ids: [],
entities: {},
});
});

it('should let you update an entity in the state', () => {
const withOne = adapter.addOne(TheGreatGatsby, state);
const changes = { title: 'A New Hope' };

const withUpdates = adapter.updateOne(
{
id: TheGreatGatsby.id,
changes,
},
withOne
);

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(
{
id: TheGreatGatsby.id,
changes: { title: 'A New Title' },
},
state
);

expect(withUpdates).toEqual(state);
});

it('should let you update the id of entity', () => {
const withOne = adapter.addOne(TheGreatGatsby, state);
const changes = { id: 'A New Id' };

const withUpdates = adapter.updateOne(
{
id: TheGreatGatsby.id,
changes,
},
withOne
);

expect(withUpdates).toEqual({
ids: [changes.id],
entities: {
[changes.id]: {
...TheGreatGatsby,
...changes,
},
},
});
});

it('should resort correctly if the id and sort key update', () => {
const withOne = adapter.addAll(
[TheGreatGatsby, AnimalFarm, AClockworkOrange],
state
);
const changes = { id: 'A New Id', title: AnimalFarm.title };

const withUpdates = adapter.updateOne(
{
id: TheGreatGatsby.id,
changes,
},
withOne
);

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

it('should let you update many entities in the state', () => {
const firstChange = { title: 'Zack' };
const secondChange = { title: 'Aaron' };
const withMany = adapter.addAll([TheGreatGatsby, AClockworkOrange], state);

const withUpdates = adapter.updateMany(
[
{ id: TheGreatGatsby.id, changes: firstChange },
{ id: AClockworkOrange.id, changes: secondChange },
],
withMany
);

expect(withUpdates).toEqual({
ids: [AClockworkOrange.id, TheGreatGatsby.id],
entities: {
[TheGreatGatsby.id]: {
...TheGreatGatsby,
...firstChange,
},
[AClockworkOrange.id]: {
...AClockworkOrange,
...secondChange,
},
},
});
});
});
Loading

0 comments on commit 9bdfd70

Please sign in to comment.