Merge pull request #1785 from bendiy/4_6_x
[xtuple] / test / lib / smoke.js
1 (function () {
2   "use strict";
3
4   var _ = require("underscore"),
5     assert = require("chai").assert;
6
7   var navigateToList = exports.navigateToList = function (app, listKind) {
8     var navigator = app.$.postbooks.$.navigator,
9       myModuleIndex,
10       myPanelIndex;
11
12     //
13     // Drill down into the appropriate module
14     //
15     _.each(navigator.modules, function (module, moduleIndex) {
16       _.each(module.panels, function (panel, panelIndex) {
17         if (listKind && panel.kind === listKind) {
18           myModuleIndex = moduleIndex;
19           myPanelIndex = panelIndex;
20         }
21       });
22     });
23     assert.isDefined(myPanelIndex, "Cannot find " + listKind + " in any module panels");
24     navigator.setModule(myModuleIndex);
25     navigator.setContentPanel(myPanelIndex);
26     return navigator;
27   };
28
29   /**
30     Finds the list in the panels and opens up a new workspace from that list.
31   */
32   var navigateToNewWorkspace = exports.navigateToNewWorkspace = function (app, listKind, done) {
33     var navigator, workspaceContainer, model, autoRegex, eventName, idChanged;
34
35     navigator = navigateToList(app, listKind);
36     //
37     // Create a new record
38     //
39     navigator.newRecord({}, {originator: {}});
40     workspaceContainer = app.$.postbooks.getActive();
41     assert.isDefined(workspaceContainer);
42     assert.equal(workspaceContainer.kind, "XV.WorkspaceContainer");
43     model = workspaceContainer.$.workspace.value;
44
45     autoRegex = XM.Document.AUTO_NUMBER + "|" + XM.Document.AUTO_OVERRIDE_NUMBER;
46     if (model instanceof XM.Document && model.numberPolicy.match(autoRegex)) {
47       // wait for the model to fetch its id if appropriate
48       if (model.id) {
49         // the id is already defined? No need to wait for it from the server, then.
50         done(workspaceContainer);
51         return;
52       }
53       eventName = "change:" + model.idAttribute;
54       idChanged = function () {
55         if (model.id) {
56           model.off(eventName, idChanged);
57           done(workspaceContainer);
58         }
59       };
60       model.on(eventName, idChanged);
61     } else {
62       done(workspaceContainer);
63     }
64   };
65
66   var navigateToExistingWorkspace = exports.navigateToExistingWorkspace = function (app, listKind, done) {
67     var workspaceContainer,
68       workspace,
69       navigator = navigateToList(app, listKind),
70       list = navigator.$.contentPanels.getActive(),
71       collection = list.value,
72
73       /**
74        * Open workspace backed by the given model.
75        * @param model
76        */
77       navigate = function (model, status, options) {
78         /**
79          * This lets us begin listening for events from workspace.value before
80          * that value exists.
81          * @listens lock:obtain
82          */
83         XM.Tuplespace.once('lock:obtain', function (_model, lock) {
84           // console.log('\na ' + _model.recordType + ' obtained lock!');
85
86           workspaceContainer = app.$.postbooks.getActive();
87           assert.isDefined(workspaceContainer);
88
89           workspace = workspaceContainer.$.workspace;
90           assert.isDefined(workspace);
91           assert.isDefined(workspace.value);
92
93           if (_model.id !== workspace.value.id) {
94             return;
95           }
96
97           done(workspaceContainer);
98         });
99
100         /**
101          * Create workspace.
102          * @fires onWorkspace
103          */
104         navigator.doWorkspace({
105           workspace: list.getWorkspace(),
106           id: model.id
107         });
108       };
109
110     /**
111      * Navigate to workspace of first model in the list.
112      */
113     if (collection.getStatus() === XM.Model.READY_CLEAN) {
114       navigate(collection.at(0));
115     } else {
116       collection.once('status:READY_CLEAN', navigate);
117     }
118   };
119
120   /**
121     Applies the attributes to the model by bubbling up the values
122     from the widgets in the workspace.
123    */
124   var setWorkspaceAttributes = exports.setWorkspaceAttributes = function (workspace, createHash) {
125     _.each(createHash, function (value, key) {
126       var widgetFound = false,
127         attribute;
128
129       _.each(workspace.$, function (widget) {
130         if (widget.attr === key) {
131           widgetFound = true;
132           widget.doValueChange({value: value});
133         }
134       });
135       assert.isTrue(widgetFound, "Cannot find widget for attr " + key + " in workspace " + workspace.kind);
136       attribute = workspace.value.get(key);
137       if (attribute.idAttribute && !value.idAttribute) {
138         // the attribute has been turned into a model
139         assert.equal(attribute.id, value[attribute.idAttribute]);
140
141       } else if (key === workspace.value.idAttribute && workspace.value.enforceUpperKey) {
142         // the model uppercases the key
143         assert.equal(workspace.value.get(key), value.toUpperCase());
144
145       } else {
146         assert.equal(workspace.value.get(key), value);
147       }
148     });
149   };
150
151   /**
152     Save the model through the workspace and make sure it saved ok.
153    */
154   var saveWorkspace = exports.saveWorkspace = function (workspace, done, skipValidation) {
155     var invalid = function (model, err) {
156       workspace.value.off('invalid', invalid);
157       done(err);
158     };
159
160     assert.isTrue(workspace.value.hasLockKey(), "Cannot acquire lock key");
161
162     if (!skipValidation) {
163       var validation = workspace.value.validate(workspace.value.attributes);
164       assert.isUndefined(validation, "Failed validation with error: " + JSON.stringify(validation));
165     }
166
167     workspace.value.on('invalid', invalid);
168     //workspace.value.on('all', function (event, model, err) {
169     //  console.log("save event", event, model && model.id);
170     //});
171     workspace.save({
172       // wait until the list has been refreshed with this model before we return control
173       modelChangeDone: function () {
174         var lockChange = function () {
175           workspace.value.off("lockChange", lockChange);
176           done(null, workspace.value);
177         };
178         workspace.value.on("lockChange", lockChange);
179         workspace.value.releaseLock();
180       },
181       error: function (err) {
182         assert.fail(JSON.stringify(err));
183       }
184     });
185   };
186
187   var deleteFromList = exports.deleteFromList = function (app, model, done) {
188     var statusChange;
189
190     // back up to list
191     app.$.postbooks.previous();
192     if (app.$.postbooks.getActive().kind === "XV.WorkspaceContainer") {
193       console.log("Ok, we want to be in the navigator by now");
194       console.log("Model status is", model.getStatusString());
195       console.log("Notify popup showing?", XT.app.$.postbooks.$.notifyPopup.showing);
196       console.log("Notify popup message", XT.app.$.postbooks.$.notifyMessage.getContent());
197     }
198     assert.equal(app.$.postbooks.getActive().kind, "XV.Navigator");
199
200     // here's the list
201     var list = app.$.postbooks.getActive().$.contentPanels.getActive(),
202       // find the new model by id
203       // TODO: what if the new model is off the page and cannot be found?
204       listModel = _.find(list.value.models, function (m) {
205         return m.get(m.idAttribute) === model.id;
206       });
207
208     statusChange = function (model, status) {
209       if (status === XM.Model.DESTROYED_DIRTY) {
210         model.off("statusChange", statusChange);
211         assert.equal(XT.app.$.postbooks.getActive().kind, "XV.Navigator");
212         // XXX we have to wait for the list to know the model is gone,
213         // or else the next test might pick it up in BUSY_FETCHING status
214         setTimeout(function () {
215           done();
216         }, 3000);
217       }
218     };
219     model.on("statusChange", statusChange);
220
221     // delete it, by calling the function that gets called when the user ok's the delete popup
222     list.deleteItem({model: listModel});
223   };
224
225   exports.updateFirstModel = function (test) {
226     it('should allow a trivial update to the first model of ' + test.kind, function (done) {
227       this.timeout(20 * 1000);
228       navigateToExistingWorkspace(XT.app, test.kind, function (workspaceContainer) {
229         var updateObj,
230           statusChanged,
231           workspace = workspaceContainer.$.workspace;
232
233         assert.equal(workspace.value.recordType, test.model);
234         if (typeof test.update === 'string') {
235           updateObj = {};
236           updateObj[test.update] = "Test" + Math.random();
237         } else if (typeof test.update === 'object') {
238           updateObj = test.update;
239         }
240         statusChanged = function () {
241           if (workspace.value.getStatus() === XM.Model.READY_CLEAN) {
242             workspace.value.off("statusChange", statusChanged);
243             setWorkspaceAttributes(workspace, updateObj);
244             saveWorkspace(workspace, function () {
245               XT.app.$.postbooks.previous();
246               assert.equal(XT.app.$.postbooks.getActive().kind, "XV.Navigator");
247               done();
248             });
249           }
250         };
251         workspace.value.on("statusChange", statusChanged);
252       });
253     });
254   };
255
256   exports.runUICrud = function (spec) {
257     var workspaceContainer,
258       workspace;
259     it('can get to a new workspace', function (done) {
260       this.timeout(10 * 1000);
261       navigateToNewWorkspace(XT.app, spec.listKind, function (_workspaceContainer) {
262         workspaceContainer = _workspaceContainer;
263         done();
264       });
265     });
266     it('can set the workspace attributes', function () {
267       workspace = workspaceContainer.$.workspace;
268       assert.equal(workspace.value.recordType, spec.recordType);
269       setWorkspaceAttributes(workspace, spec.createHash);
270     });
271     _.each(spec.beforeSaveUIActions || [], function (spec) {
272       it(spec.it, function (done) {
273         this.timeout(20 * 1000);
274         spec.action(workspace, done);
275       });
276     });
277     it('can save the workspace', function (done) {
278       this.timeout(20 * 1000);
279       if (spec.captureObject) {
280         XG = XG || {};
281         XG.capturedId = workspace.value.id;
282       }
283       saveWorkspace(workspace, done);
284     });
285     _.each(spec.afterSaveUIActions || [], function (spec) {
286       it(spec.it, function (done) {
287         this.timeout(20 * 1000);
288         spec.action(workspace, done);
289       });
290     });
291     if (spec.captureObject) {
292       return;
293     }
294     it('can delete the item from the list', function (done) {
295       this.timeout(20 * 1000);
296       deleteFromList(XT.app, workspace.value, done);
297     });
298   };
299 }());