PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/spec/javascripts/models/story_spec.js

https://bitbucket.org/sqctest01/fulcrum
JavaScript | 416 lines | 312 code | 98 blank | 6 comment | 2 complexity | 42d3b9de5aa09de61ab54750f49439b0 MD5 | raw file
  1. describe('Fulcrum.Story', function() {
  2. beforeEach(function() {
  3. var Project = Backbone.Model.extend({
  4. name: 'project',
  5. defaults: {point_values: [0, 1, 2, 3]},
  6. users: { get: function() {} },
  7. current_user: { id: 999 },
  8. currentIterationNumber: function() { return 1; },
  9. getIterationNumberForDate: function() { return 999; }
  10. });
  11. var collection = {
  12. project: new Project({}), url: '/foo', remove: function() {},
  13. get: function() {}
  14. };
  15. var view = new Backbone.View();
  16. this.story = new Fulcrum.Story({
  17. id: 999, title: 'Test story', position: '2.45'
  18. });
  19. this.new_story = new Fulcrum.Story({
  20. title: 'New story'
  21. });
  22. this.story.collection = this.new_story.collection = collection;
  23. this.story.view = this.new_story.view = view;
  24. this.server = sinon.fakeServer.create();
  25. });
  26. describe('when instantiated', function() {
  27. it('should exhibit attributes', function() {
  28. expect(this.story.get('title'))
  29. .toEqual('Test story');
  30. });
  31. it('should have a default state of unscheduled', function() {
  32. expect(this.story.get('state'))
  33. .toEqual('unscheduled');
  34. });
  35. it('should have a default story type of feature', function() {
  36. expect(this.story.get('story_type'))
  37. .toEqual('feature');
  38. });
  39. it('should have an empty array of events by default', function() {
  40. expect(this.story.get('events'))
  41. .toEqual([]);
  42. });
  43. });
  44. describe('state transitions', function() {
  45. it('should start', function() {
  46. this.story.start();
  47. expect(this.story.get('state')).toEqual('started');
  48. });
  49. it('should finish', function() {
  50. this.story.finish();
  51. expect(this.story.get('state')).toEqual('finished');
  52. });
  53. it('should deliver', function() {
  54. this.story.deliver();
  55. expect(this.story.get('state')).toEqual('delivered');
  56. });
  57. it('should accept', function() {
  58. this.story.accept();
  59. expect(this.story.get('state')).toEqual('accepted');
  60. });
  61. it('should reject', function() {
  62. this.story.reject();
  63. expect(this.story.get('state')).toEqual('rejected');
  64. });
  65. it('should restart', function() {
  66. this.story.restart();
  67. expect(this.story.get('state')).toEqual('started');
  68. });
  69. });
  70. describe("events", function() {
  71. it("transitions from unscheduled", function() {
  72. this.story.set({state: "unscheduled"});
  73. expect(this.story.events()).toEqual(["start"]);
  74. });
  75. it("transitions from unstarted", function() {
  76. this.story.set({state: "unstarted"});
  77. expect(this.story.events()).toEqual(["start"]);
  78. });
  79. it("transitions from started", function() {
  80. this.story.set({state: "started"});
  81. expect(this.story.events()).toEqual(["finish"]);
  82. });
  83. it("transitions from finished", function() {
  84. this.story.set({state: "finished"});
  85. expect(this.story.events()).toEqual(["deliver"]);
  86. });
  87. it("transitions from delivered", function() {
  88. this.story.set({state: "delivered"});
  89. expect(this.story.events()).toEqual(["accept", "reject"]);
  90. });
  91. it("transitions from rejected", function() {
  92. this.story.set({state: "rejected"});
  93. expect(this.story.events()).toEqual(["restart"]);
  94. });
  95. it("has no transitions from accepted", function() {
  96. this.story.set({state: "accepted"});
  97. expect(this.story.events()).toEqual([]);
  98. });
  99. });
  100. describe("setAcceptedAt", function() {
  101. it("should set accepted at to today's date when accepted", function() {
  102. var today = new Date();
  103. today.setHours(0);
  104. today.setMinutes(0);
  105. today.setSeconds(0);
  106. today.setMilliseconds(0);
  107. expect(this.story.get('accepted_at')).toBeUndefined();
  108. this.story.accept();
  109. expect(new Date(this.story.get('accepted_at'))).toEqual(today);
  110. });
  111. it("should not set accepted at when accepted if already set", function() {
  112. this.story.set({accepted_at: "2001/01/01"});
  113. this.story.accept();
  114. expect(this.story.get('accepted_at')).toEqual("2001/01/01");
  115. });
  116. });
  117. describe('estimable', function() {
  118. describe("when story is a feature", function() {
  119. beforeEach(function() {
  120. this.story.set({story_type: 'feature'});
  121. });
  122. it('should be estimable when not estimated', function() {
  123. sinon.stub(this.story, 'estimated').returns(false);
  124. expect(this.story.estimable()).toBeTruthy();
  125. });
  126. it('should not be estimable when estimated', function() {
  127. sinon.stub(this.story, 'estimated').returns(true);
  128. expect(this.story.estimable()).toBeFalsy();
  129. });
  130. });
  131. });
  132. describe('estimated', function() {
  133. it('should say if it is estimated or not', function() {
  134. this.story.unset('estimate');
  135. expect(this.story.estimated()).toBeFalsy();
  136. this.story.set({estimate: null});
  137. expect(this.story.estimated()).toBeFalsy();
  138. this.story.set({estimate: 0});
  139. expect(this.story.estimated()).toBeTruthy();
  140. this.story.set({estimate: 1});
  141. expect(this.story.estimated()).toBeTruthy();
  142. });
  143. });
  144. describe('point_values', function() {
  145. it('should known about its valid points values', function() {
  146. expect(this.story.point_values()).toEqual([0, 1, 2, 3]);
  147. });
  148. });
  149. describe('class name', function() {
  150. it('should have a classes of story and story type', function() {
  151. this.story.set({estimate: 1});
  152. expect(this.story.className()).toEqual('story feature');
  153. });
  154. it('should have an unestimated class if unestimated', function() {
  155. expect(this.story.estimable()).toBeTruthy();
  156. expect(this.story.estimated()).toBeFalsy();
  157. expect(this.story.className()).toEqual('story feature unestimated');
  158. });
  159. });
  160. describe('position', function() {
  161. it('should get position as a float', function() {
  162. expect(this.story.position()).toEqual(2.45);
  163. });
  164. });
  165. describe('column', function() {
  166. it('should return the right column', function() {
  167. this.story.set({state: 'unscheduled'});
  168. expect(this.story.column).toEqual('#chilly_bin');
  169. this.story.set({state: 'unstarted'});
  170. expect(this.story.column).toEqual('#backlog');
  171. this.story.set({state: 'started'});
  172. expect(this.story.column).toEqual('#in_progress');
  173. this.story.set({state: 'delivered'});
  174. expect(this.story.column).toEqual('#in_progress');
  175. this.story.set({state: 'rejected'});
  176. expect(this.story.column).toEqual('#in_progress');
  177. // If the story is accepted, but it's accepted_at date is within the
  178. // current iteration, it should be in the in_progress column, otherwise
  179. // it should be in the #done column
  180. sinon.stub(this.story, 'iterationNumber').returns(1);
  181. this.story.collection.project.currentIterationNumber = sinon.stub().returns(2);
  182. this.story.set({state: 'accepted'});
  183. expect(this.story.column).toEqual('#done');
  184. this.story.collection.project.currentIterationNumber.returns(1);
  185. this.story.setColumn();
  186. expect(this.story.column).toEqual('#in_progress');
  187. });
  188. });
  189. describe("clear", function() {
  190. it("should destroy itself and its view", function() {
  191. var model_spy = sinon.spy(this.story, "destroy");
  192. var view_spy = sinon.spy(this.story.view, "remove");
  193. this.story.clear();
  194. expect(model_spy).toHaveBeenCalled();
  195. expect(view_spy).toHaveBeenCalled();
  196. });
  197. it("should not call destroy if its a new object", function() {
  198. var spy = sinon.spy(this.new_story, 'destroy');
  199. var view_spy = sinon.spy(this.new_story.view, "remove");
  200. this.new_story.clear();
  201. expect(spy).not.toHaveBeenCalled();
  202. expect(view_spy).toHaveBeenCalled();
  203. });
  204. });
  205. describe("errors", function() {
  206. it("should record errors", function() {
  207. expect(this.story.hasErrors()).toBeFalsy();
  208. expect(this.story.errorsOn('title')).toBeFalsy();
  209. this.story.set({errors: {
  210. title: ["cannot be blank", "needs to be better"],
  211. estimate: ["is helluh unrealistic"]
  212. }});
  213. expect(this.story.hasErrors()).toBeTruthy();
  214. expect(this.story.errorsOn('title')).toBeTruthy();
  215. expect(this.story.errorsOn('position')).toBeFalsy();
  216. expect(this.story.errorMessages())
  217. .toEqual("title cannot be blank, title needs to be better, estimate is helluh unrealistic");
  218. });
  219. });
  220. describe("users", function() {
  221. it("should get it's owner", function() {
  222. // Should return undefined if the story does not have an owner
  223. var spy = sinon.spy(this.story.collection.project.users, "get");
  224. var owned_by = this.story.owned_by();
  225. expect(spy).toHaveBeenCalledWith(undefined);
  226. expect(owned_by).toBeUndefined();
  227. this.story.set({'owned_by_id': 999});
  228. owned_by = this.story.owned_by();
  229. expect(spy).toHaveBeenCalledWith(999);
  230. });
  231. it("should get it's requester", function() {
  232. // Should return undefined if the story does not have an owner
  233. var stub = sinon.stub(this.story.collection.project.users, "get");
  234. var dummyUser = {};
  235. stub.withArgs(undefined).returns(undefined);
  236. stub.withArgs(999).returns(dummyUser);
  237. var requested_by = this.story.requested_by();
  238. expect(stub).toHaveBeenCalledWith(undefined);
  239. expect(requested_by).toBeUndefined();
  240. this.story.set({'requested_by_id': 999});
  241. requested_by = this.story.requested_by();
  242. expect(requested_by).toEqual(dummyUser);
  243. expect(stub).toHaveBeenCalledWith(999);
  244. });
  245. it("should return a readable created_at", function() {
  246. var timestamp = "2011/09/19 02:25:56 +0000";
  247. this.story.set({'created_at': timestamp});
  248. expect(this.story.created_at()).toBe(
  249. new Date(timestamp).format(this.story.timestampFormat)
  250. );
  251. });
  252. it("should be assigned to the current user when started", function() {
  253. expect(this.story.get('state')).toEqual('unscheduled');
  254. expect(this.story.owned_by()).toBeUndefined();
  255. this.story.set({state: 'started'});
  256. expect(this.story.get('owned_by_id')).toEqual(999);
  257. });
  258. });
  259. describe("details", function() {
  260. // If the story has details other than the title, e.g. description
  261. it("should return true the story has a description", function() {
  262. expect(this.story.hasDetails()).toBeFalsy();
  263. this.story.set({description: "Test description"});
  264. expect(this.story.hasDetails()).toBeTruthy();
  265. });
  266. it("should return true if the story has saved notes", function() {
  267. expect(this.story.hasDetails()).toBeFalsy();
  268. this.story.hasNotes = sinon.stub().returns(true);
  269. expect(this.story.hasDetails()).toBeTruthy();
  270. });
  271. });
  272. describe("iterations", function() {
  273. it("should return the iteration number for an accepted story", function() {
  274. this.story.collection.project.getIterationNumberForDate = sinon.stub().returns(999);
  275. this.story.set({accepted_at: "2011/07/25", state: "accepted"});
  276. expect(this.story.iterationNumber()).toEqual(999);
  277. });
  278. });
  279. describe("labels", function() {
  280. it("should return an empty array if labels undefined", function() {
  281. expect(this.story.get('labels')).toBeUndefined();
  282. expect(this.story.labels()).toEqual([]);
  283. });
  284. it("should return an array of labels", function() {
  285. this.story.set({labels: "foo,bar"});
  286. expect(this.story.labels()).toEqual(["foo","bar"]);
  287. });
  288. it("should remove trailing and leading whitespace when spliting labels", function() {
  289. this.story.set({labels: "foo , bar , baz"});
  290. expect(this.story.labels()).toEqual(["foo","bar","baz"]);
  291. });
  292. });
  293. describe("notes", function() {
  294. it("should default with an empty notes collection", function() {
  295. expect(this.story.notes.length).toEqual(0);
  296. });
  297. it("should set the right notes collection url", function() {
  298. expect(this.story.notes.url()).toEqual('/foo/999/notes');
  299. });
  300. it("should set a notes collection", function() {
  301. var story = new Fulcrum.Story({
  302. notes: [{"note":{"text": "Dummy note"}}]
  303. });
  304. expect(story.notes.length).toEqual(1);
  305. });
  306. describe("hasNotes", function() {
  307. it("returns true if it has saved notes", function() {
  308. expect(this.story.hasNotes()).toBeFalsy();
  309. this.story.notes.add({id: 999, note: "Test note"});
  310. expect(this.story.hasNotes()).toBeTruthy();
  311. });
  312. it("returns false if it has unsaved notes", function() {
  313. this.story.notes.add({note: "Unsaved note"});
  314. expect(this.story.hasNotes()).toBeFalsy();
  315. });
  316. });
  317. });
  318. });