lore
Version:
Convention-driven framework for building React-Redux applications
494 lines (404 loc) • 16.3 kB
JavaScript
var expect = require('chai').expect;
var _ = require('lodash');
var nock = require('nock');
var Lore = require('../../src/app/index');
var loaderHelper = require('../helpers/loaderHelper');
var populateStore = require('../helpers/populateStore');
var config = {
hooks: require('../defaultHooks')
};
var TEST_DELAY = 50;
describe('lore#redux', function() {
var lore = null;
beforeEach(function() {
lore = new Lore();
});
beforeEach(function() {
loaderHelper.stub({
models: {
todo: {}
}
});
});
describe('action-reducer flow: actions.todo.create() [id as String]', function() {
beforeEach(function() {
nock('https://api.example.com')
.persist()
.post('/todos')
.reply(201, {
id: '1',
title: 'foo'
});
});
it("should create a todo and add it to the store", function(done) {
lore.build(config);
var optimisticTodo = lore.actions.todo.create({
title: 'foo'
}).payload;
// The first time we check state we should see the optimistic response
var state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(0);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// Subscribe to the store so we can be notified once the server response
// comes back with the real data
lore.store.subscribe(_.debounce(function() {
// The second time we check state we should see the real model
state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// Just for fun, let's verify the id actually exists
var realTodo = state.todo.byCid[optimisticTodo.cid];
expect(realTodo.id).to.exist;
done();
}, TEST_DELAY));
});
});
describe('action-reducer flow: actions.todo.find() [id as String]', function() {
beforeEach(function() {
nock('https://api.example.com')
.persist()
.get('/todos')
.reply(200, [
{
id: '1',
title: 'foo'
}
]);
});
it("should find the todos and add them to the store", function(done) {
lore.build(config);
var optimisticTodos = lore.actions.todo.find().payload;
// We should have one query dictionary created, but it should have no data
var state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(1);
expect(_.keys(state.todo.byId).length).to.equal(0);
expect(_.keys(state.todo.byCid).length).to.equal(0);
expect(state.todo.find['{"where":{},"pagination":{}}'].data.length).to.equal(0);
// Subscribe to the store so we can be notified once the server response
// comes back with the real data
lore.store.subscribe(_.debounce(function () {
// The second time we check state we should see the models returned
// from the server
state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(1);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
expect(state.todo.find['{"where":{},"pagination":{}}'].data.length).to.equal(1);
done();
}, TEST_DELAY));
});
});
describe('action-reducer flow: actions.todo.get() [id as String]', function() {
beforeEach(function() {
nock('https://api.example.com')
.persist()
.get('/todos/1')
.reply(200, {
id: '1',
title: 'foo'
});
});
it("should create a todo, add it to the store, and update it when the server responds", function(done) {
lore.build(config);
var optimisticTodo = lore.actions.todo.get('1').payload;
// Because we're specifying the id, it should be in two reducers
var state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// Subscribe to the store so we can be notified once the server response
// comes back with the real data
lore.store.subscribe(_.debounce(function () {
// The second time we check state we should see the models returned
// from the server
state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// We should have the title in the store now if the server truly responded
var realTodo = state.todo.byId['1'];
expect(realTodo.data.title).to.equal('foo');
done();
}, TEST_DELAY));
});
});
describe('action-reducer flow: actions.todo.update() [id as String]', function() {
beforeEach(function() {
nock('https://api.example.com')
.persist()
.put('/todos/1')
.reply(200, {
id: '1',
title: 'bar'
});
});
it("should udpate the todo and update it in the store", function(done) {
lore.build(config);
var store = lore.store;
var data = populateStore(store, {
todo: [{
id: '1',
cid: 'c1',
title: 'foo'
}]
});
var todo = data.todo[0];
var optimisticUpdatedTodo = lore.actions.todo.update(todo, {
title: 'baz'
}).payload;
// Because we're specifying the id, it should be in two reducers
var state = store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
expect(state.todo.byId[todo.id].data.title).to.equal(optimisticUpdatedTodo.data.title);
// Subscribe to the store so we can be notified once the server response
// comes back with the real data
store.subscribe(_.debounce(function () {
// The second time we check state we should see the models returned
// from the server
state = store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// We should have the title in the store now if the server truly responded
var realTodo = state.todo.byId['1'];
expect(realTodo.data.title).to.equal('bar');
done();
}, TEST_DELAY));
});
});
describe('action-reducer flow: actions.todo.destroy() [id as String]', function() {
beforeEach(function() {
nock('https://api.example.com')
.persist()
.delete('/todos/1')
.reply(200);
});
it("should delete a todo and remove it from the store", function(done) {
lore.build(config);
var store = lore.store;
var data = populateStore(store, {
todo: [{
id: '1',
cid: 'c1',
title: 'foo'
}]
});
var todo = data.todo[0];
var optimisticDestroyedTodo = lore.actions.todo.destroy(todo).payload;
// Because we're specifying the id, it should be in two reducers
var state = store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// Subscribe to the store so we can be notified once the server confirms
// the request
store.subscribe(_.debounce(function () {
// The second time we check state the delete request will have completed
// and the model should have a state of 'DELETED'
state = store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(state.todo.byId[_.keys(state.todo.byId)[0]].state).to.equal('DELETED');
expect(_.keys(state.todo.byCid).length).to.equal(1);
expect(state.todo.byCid[_.keys(state.todo.byCid)[0]].state).to.equal('DELETED');
done();
}, TEST_DELAY));
});
});
/**
* Repeat the tests, but using a Number for the unique id
*/
describe('action-reducer flow: actions.todo.create() [id as Number]', function() {
beforeEach(function() {
nock('https://api.example.com')
.persist()
.post('/todos')
.reply(201, {
id: 1,
title: 'foo'
});
});
it("should create a todo and add it to the store", function(done) {
lore.build(config);
var optimisticTodo = lore.actions.todo.create({
title: 'foo'
}).payload;
// The first time we check state we should see the optimistic response
var state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(0);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// Subscribe to the store so we can be notified once the server response
// comes back with the real data
lore.store.subscribe(_.debounce(function () {
// The second time we check state we should see the real model
state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// Just for fun, let's verify the id actually exists
var realTodo = state.todo.byCid[optimisticTodo.cid];
expect(realTodo.id).to.exist;
done();
}, TEST_DELAY));
});
});
describe('action-reducer flow: actions.todo.find() [id as Number]', function() {
beforeEach(function() {
nock('https://api.example.com')
.persist()
.get('/todos')
.reply(200, [
{
id: 1,
title: 'foo'
}
]);
});
it("should find the todos and add them to the store", function(done) {
lore.build(config);
var optimisticTodos = lore.actions.todo.find().payload;
// We should have one query dictionary created, but it should have no data
var state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(1);
expect(_.keys(state.todo.byId).length).to.equal(0);
expect(_.keys(state.todo.byCid).length).to.equal(0);
expect(state.todo.find['{"where":{},"pagination":{}}'].data.length).to.equal(0);
// Subscribe to the store so we can be notified once the server response
// comes back with the real data
lore.store.subscribe(_.debounce(function () {
// The second time we check state we should see the models returned
// from the server
state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(1);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
expect(state.todo.find['{"where":{},"pagination":{}}'].data.length).to.equal(1);
done();
}, TEST_DELAY));
});
});
describe('action-reducer flow: actions.todo.get() [id as Number]', function() {
beforeEach(function() {
nock('https://api.example.com')
.persist()
.get('/todos/1')
.reply(200, {
id: 1,
title: 'foo'
});
});
it("should create a todo, add it to the store, and update it when the server responds", function(done) {
lore.build(config);
var optimisticTodo = lore.actions.todo.get(1).payload;
// Because we're specifying the id, it should be in two reducers
var state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// Subscribe to the store so we can be notified once the server response
// comes back with the real data
lore.store.subscribe(_.debounce(function () {
// The second time we check state we should see the models returned
// from the server
state = lore.store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// We should have the title in the store now if the server truly responded
var realTodo = state.todo.byId[1];
expect(realTodo.data.title).to.equal('foo');
done();
}, TEST_DELAY));
});
});
describe('action-reducer flow: actions.todo.update() [id as Number]', function() {
beforeEach(function() {
nock('https://api.example.com')
.persist()
.put('/todos/1')
.reply(200, {
id: 1,
title: 'bar'
});
});
it("should udpate the todo and update it in the store", function(done) {
lore.build(config);
var store = lore.store;
var data = populateStore(store, {
todo: [{
id: 1,
cid: 'c1',
title: 'foo'
}]
});
var todo = data.todo[0];
var optimisticUpdatedTodo = lore.actions.todo.update(todo, {
title: 'baz'
}).payload;
// Because we're specifying the id, it should be in two reducers
var state = store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
expect(state.todo.byId[todo.id].data.title).to.equal(optimisticUpdatedTodo.data.title);
// Subscribe to the store so we can be notified once the server response
// comes back with the real data
store.subscribe(_.debounce(function () {
// The second time we check state we should see the models returned
// from the server
state = store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// We should have the title in the store now if the server truly responded
var realTodo = state.todo.byId['1'];
expect(realTodo.data.title).to.equal('bar');
done();
}, TEST_DELAY));
});
});
describe('action-reducer flow: actions.todo.destroy() [id as Number]', function() {
beforeEach(function() {
nock('https://api.example.com')
.persist()
.delete('/todos/1')
.reply(200);
});
it("should delete a todo and remove it from the store", function(done) {
lore.build(config);
var store = lore.store;
var data = populateStore(store, {
todo: [{
id: 1,
cid: 'c1',
title: 'foo'
}]
});
var todo = data.todo[0];
var optimisticDestroyedTodo = lore.actions.todo.destroy(todo).payload;
// Because we're specifying the id, it should be in two reducers
var state = store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(_.keys(state.todo.byCid).length).to.equal(1);
// Subscribe to the store so we can be notified once the server confirms
// the request
store.subscribe(_.debounce(function () {
// The second time we check state the delete request will have completed
// and the model should have a state of 'DELETED'
state = store.getState();
expect(_.keys(state.todo.find).length).to.equal(0);
expect(_.keys(state.todo.byId).length).to.equal(1);
expect(state.todo.byId[_.keys(state.todo.byId)[0]].state).to.equal('DELETED');
expect(_.keys(state.todo.byCid).length).to.equal(1);
expect(state.todo.byCid[_.keys(state.todo.byCid)[0]].state).to.equal('DELETED');
done();
}, TEST_DELAY));
});
});
});