angular-scaffold
Version:
Angular Scaffold is a collection of convenience wrappers around angular-model collections.
490 lines (350 loc) • 11.2 kB
JavaScript
describe("scaffold", function() {
var provider;
beforeEach(module("ur.scaffold.mocks"));
beforeEach(module("ur.scaffold", function(scaffoldProvider) {
provider = scaffoldProvider;
}));
beforeEach(module("ur.model", function(modelProvider) {
modelProvider
.model("Dogs", {
url: "http://api/dogs",
defaults: {
breed: "Unknown"
}
});
}));
beforeEach(function() {
jasmine.addMatchers({
toEqualData: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
return {
pass: angular.equals(actual, expected)
};
}
};
},
toBeDeferred: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
return {
pass: angular.isFunction(actual.resolve) &&
angular.isFunction(actual.reject) &&
angular.isFunction(actual.notify)
}
}
};
}
});
});
describe("provider", function() {
describe("configuration", function() {
it("should accept scaffold definitions", inject(function() {
expect(provider.scaffold("Dogs", {})).toBe(provider);
}));
it("should infer the model to use", inject(function(model, scaffold) {
var dogs = model("Dogs");
provider.scaffold("Dogs", {});
expect(scaffold("Dogs").model()).toEqual(dogs);
}));
it("should use a given model", inject(function(model, scaffold) {
var dogs = model("Dogs");
provider.scaffold("Canines", {
model: dogs
});
expect(scaffold("Canines").model()).toEqual(dogs);
provider.scaffold("Canines", {
model: "Dogs"
});
expect(scaffold("Canines").model()).toEqual(dogs);
}));
it("should work without configuration", inject(function(model, scaffold) {
var dogs = model("Dogs");
var s = scaffold("Dogs", {});
expect(s.model()).toEqual(dogs);
}));
});
});
describe("service", function() {
var http, mocks, scaffold;
beforeEach(inject(function($injector) {
http = $injector.get('$httpBackend');
mocks = $injector.get('mocks');
scaffold = $injector.get('scaffold');
provider.scaffold("Dogs", {});
}));
describe("fetch all", function() {
it("should GET and store", function() {
var s = scaffold("Dogs");
http.expectGET("http://api/dogs").respond(mocks.all);
http.flush();
expect(s.items).toEqualData(mocks.all);
});
it("should GET and store with default query", function() {
var s = scaffold("Dogs", {
query: { breed: "boxer" }
});
http.expectGET("http://api/dogs?breed=boxer").respond(mocks.boxers);
http.flush();
expect(s.items).toEqualData(mocks.boxers);
});
it("should apply a given query", function() {
var s = scaffold("Dogs");
http.whenGET("http://api/dogs").respond(mocks.all);
http.flush();
expect(s.items).toEqualData(mocks.all);
s.query({ breed: "boxer" });
http.expectGET("http://api/dogs?breed=boxer").respond(mocks.boxers);
http.flush();
expect(s.items).toEqualData(mocks.boxers);
});
it("should set the loading ui state", function() {
var s = scaffold("Dogs");
expect(s.$ui.loading).toBe(true);
http.whenGET("http://api/dogs").respond(mocks.all);
http.flush();
expect(s.$ui.loading).toBe(false);
});
// @todo: use a promise instead? then how do we chain?
it("should invoke an optional callback after refresh", function() {
var fn = jasmine.createSpy('post-refresh callback');
var s = scaffold("Dogs", {
callback: fn
});
expect(fn).not.toHaveBeenCalled();
http.whenGET("http://api/dogs").respond(mocks.all);
http.flush();
expect(fn).toHaveBeenCalled();
expect(fn.calls.first().args[0]).toEqualData(mocks.all);
});
it("should handle error responses", function() {
var s = scaffold("Dogs");
expect(s.$ui.loading).toBe(true);
http.whenGET("http://api/dogs").respond(404);
http.flush();
expect(s.$ui.loading).toBe(false);
expect(s.items).toEqual([]);
});
});
describe('pagination', function() {
var headerIncludes = function(name, value) {
return function(headers) {
return headers[name] && headers[name] == value;
};
};
it("should use the range request header", function() {
var s = scaffold("Dogs", {
paginate: true
});
http.expectGET("http://api/dogs", headerIncludes("Range", "resources=0-9"))
.respond(mocks.all);
http.flush();
});
it("should return a count of pages", function() {
var s = scaffold("Dogs", {
paginate: true
});
http.whenGET("http://api/dogs").respond(mocks.boxers, {
'Content-Range': "resources 0-9/40"
});
http.flush();
expect(s.pages).toEqual(4);
});
it("should handle unknown totals", function() {
var s = scaffold("Dogs", {
paginate: true
});
http.whenGET("http://api/dogs").respond(mocks.boxers, {
'Content-Range': "resources 0-9/*"
});
http.flush();
expect(s.pages).toEqual(null);
});
it("should GET a given page", function() {
var s = scaffold("Dogs", {
paginate: true
});
http.whenGET("http://api/dogs").respond(mocks.boxers);
http.flush();
s.page(4);
http.expectGET("http://api/dogs", headerIncludes('Range', "resources=30-39"))
.respond(mocks.all);
http.flush();
});
it("should allow custom page sizes", function() {
var s = scaffold("Dogs", {
paginate: {
size: 5
}
});
http.expectGET("http://api/dogs", headerIncludes('Range', "resources=0-4"))
.respond(mocks.all);
http.flush();
});
it("should allow querying with page", function() {
var s = scaffold("Dogs", {
paginate: true
});
http.whenGET("http://api/dogs").respond(mocks.boxers);
http.flush();
s.query({breed: 'boxers'}, 4);
http.expectGET("http://api/dogs?breed=boxers", headerIncludes('Range', "resources=30-39"))
.respond(mocks.all);
http.flush();
});
it("should default to 'paged' strategy", function() {
var s = scaffold("Dogs", {
paginate: { size: 1 }
});
http.whenGET("http://api/dogs").respond([mocks.all[0]]);
http.flush();
expect(s.items).toEqualData([mocks.all[0]]);
s.page(1);
http.expectGET("http://api/dogs").respond([mocks.all[1]]);
http.flush();
expect(s.items).toEqualData([mocks.all[1]]);
});
it("should support 'infinite' strategy", function() {
var s = scaffold("Dogs", {
paginate: { size: 1, strategy: 'infinite' }
});
http.whenGET("http://api/dogs").respond([mocks.all[0]]);
http.flush();
expect(s.items).toEqualData([mocks.all[0]]);
s.page(1);
http.expectGET("http://api/dogs").respond([mocks.all[1]]);
http.flush();
expect(s.items).toEqualData(mocks.all);
});
});
describe("create", function() {
var s;
beforeEach(function() {
s = scaffold("Dogs");
http.whenGET("http://api/dogs").respond(mocks.all);
http.flush();
});
it("should return a deferred", function() {
expect(s.create()).toBeDeferred();
});
it("should hang the model instance from the deferred", function() {
var dfd = s.create();
expect(angular.isObject(dfd.$instance)).toBe(true);
expect(dfd.$instance.breed).toEqual("Unknown");
});
it("should create a new object when the deferred is resolved", function() {
var dfd = s.create();
angular.extend(dfd.$instance, mocks.one);
dfd.resolve();
http.expectPOST("http://api/dogs", mocks.one).respond(mocks.one);
http.flush();
expect(s.items.length).toEqual(3);
expect(s.items[2]).toEqualData(mocks.one);
});
it("should not save if the save option is false", function() {
var dfd = s.create();
angular.extend(dfd.$instance, mocks.one);
dfd.resolve({
save: false
});
http.verifyNoOutstandingExpectation();
expect(s.items.length).toEqual(3);
expect(s.items[2]).toEqualData(mocks.one);
});
it("should set the saving ui state", inject(function($rootScope) {
http.whenPOST("http://api/dogs", mocks.one).respond(mocks.one);
var dfd = s.create();
expect(s.$ui.saving).toBe(false);
angular.extend(dfd.$instance, mocks.one);
dfd.resolve();
$rootScope.$digest();
expect(s.$ui.saving).toBe(true);
http.flush();
expect(s.$ui.saving).toBe(false);
}));
});
// @todo: discuss approach to editing/deleting an item. Currently by index.
describe("edit", function() {
var s;
beforeEach(function() {
s = scaffold("Dogs");
http.whenGET("http://api/dogs").respond(mocks.all);
http.flush();
});
it("should return a deferred", function() {
expect(s.edit(0)).toBeDeferred();
});
it("should hang a copy of the model instance from the deferred", function() {
var dfd = s.edit(0);
expect(angular.isObject(dfd.$instance)).toBe(true);
expect(dfd.$instance.breed).toEqual("jack russel");
dfd.$instance.breed = "boxer";
expect(s.items[0].breed).toEqual("jack russel");
});
it("should update the object when the deferred is resolved", function() {
var dfd = s.edit(0);
angular.extend(dfd.$instance, {
name: "Digby"
});
dfd.resolve();
http.expectPATCH("http://api/dogs/jerry", {"name":"Digby"}).respond(204);
http.flush();
expect(s.items[0].name).toEqual("Digby");
});
it("should not update if the save option is false", function() {
var dfd = s.edit(0);
angular.extend(dfd.$instance, {
name: "Digby"
});
dfd.resolve({
save: false
});
http.verifyNoOutstandingExpectation();
expect(s.items[0].name).toEqual("Digby");
});
it("should set the saving ui state", inject(function($rootScope) {
var dfd = s.edit(0);
expect(s.$ui.saving).toBe(false);
angular.extend(dfd.$instance, {
name: "Digby"
});
http.expectPATCH("http://api/dogs/jerry", {"name":"Digby"}).respond(204);
dfd.resolve();
$rootScope.$digest();
expect(s.$ui.saving).toBe(true);
http.flush();
expect(s.$ui.saving).toBe(false);
}));
});
describe("delete", function() {
var s;
beforeEach(function() {
s = scaffold("Dogs");
http.whenGET("http://api/dogs").respond(mocks.all);
http.flush();
});
it("should return a deferred", function() {
expect(s.delete(0)).toBeDeferred();
});
it("should hang the model instance from the deferred", function() {
var dfd = s.delete(0);
expect(angular.isObject(dfd.$instance)).toBe(true);
expect(dfd.$instance.breed).toEqual("jack russel");
});
it("should delete the object when the deferred is resolved", function() {
s.delete(0).resolve();
http.expectDELETE("http://api/dogs/jerry").respond(204);
http.flush();
});
it("should set the saving ui state", inject(function($rootScope) {
http.whenDELETE("http://api/dogs/jerry").respond(204);
expect(s.$ui.saving).toBe(false);
s.delete(0).resolve();
$rootScope.$digest();
expect(s.$ui.saving).toBe(true);
http.flush();
expect(s.$ui.saving).toBe(false);
}));
});
});
});