Merge pull request #1609 from xtuple/4_5_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     assert.equal(app.$.postbooks.getActive().kind, "XV.Navigator");
193
194     // here's the list
195     var list = app.$.postbooks.getActive().$.contentPanels.getActive(),
196       // find the new model by id
197       // TODO: what if the new model is off the page and cannot be found?
198       listModel = _.find(list.value.models, function (m) {
199         return m.get(m.idAttribute) === model.id;
200       });
201
202     statusChange = function (model, status) {
203       if (status === XM.Model.DESTROYED_DIRTY) {
204         model.off("statusChange", statusChange);
205         assert.equal(XT.app.$.postbooks.getActive().kind, "XV.Navigator");
206         // XXX we have to wait for the list to know the model is gone,
207         // or else the next test might pick it up in BUSY_FETCHING status
208         setTimeout(function () {
209           done();
210         }, 3000);
211       }
212     };
213     model.on("statusChange", statusChange);
214
215     // delete it, by calling the function that gets called when the user ok's the delete popup
216     list.deleteItem({model: listModel});
217   };
218
219   exports.updateFirstModel = function (test) {
220     it('should allow a trivial update to the first model of ' + test.kind, function (done) {
221       this.timeout(20 * 1000);
222       navigateToExistingWorkspace(XT.app, test.kind, function (workspaceContainer) {
223         var updateObj,
224           statusChanged,
225           workspace = workspaceContainer.$.workspace;
226
227         assert.equal(workspace.value.recordType, test.model);
228         if (typeof test.update === 'string') {
229           updateObj = {};
230           updateObj[test.update] = "Test" + Math.random();
231         } else if (typeof test.update === 'object') {
232           updateObj = test.update;
233         }
234         statusChanged = function () {
235           if (workspace.value.getStatus() === XM.Model.READY_CLEAN) {
236             workspace.value.off("statusChange", statusChanged);
237             setWorkspaceAttributes(workspace, updateObj);
238             saveWorkspace(workspace, function () {
239               XT.app.$.postbooks.previous();
240               assert.equal(XT.app.$.postbooks.getActive().kind, "XV.Navigator");
241               done();
242             });
243           }
244         };
245         workspace.value.on("statusChange", statusChanged);
246       });
247     });
248   };
249
250   exports.runUICrud = function (spec) {
251     var workspaceContainer,
252       workspace;
253     it('can get to a new workspace', function (done) {
254       this.timeout(10 * 1000);
255       navigateToNewWorkspace(XT.app, spec.listKind, function (_workspaceContainer) {
256         workspaceContainer = _workspaceContainer;
257         done();
258       });
259     });
260     it('can set the workspace attributes', function () {
261       workspace = workspaceContainer.$.workspace;
262       assert.equal(workspace.value.recordType, spec.recordType);
263       setWorkspaceAttributes(workspace, spec.createHash);
264     });
265     _.each(spec.beforeSaveUIActions || [], function (spec) {
266       it(spec.it, function (done) {
267         this.timeout(20 * 1000);
268         spec.action(workspace, done);
269       });
270     });
271     it('can save the workspace', function (done) {
272       this.timeout(20 * 1000);
273       if (spec.captureObject) {
274         XG = XG || {};
275         XG.capturedId = workspace.value.id;
276       }
277       saveWorkspace(workspace, done);
278     });
279     if (spec.captureObject) {
280       return;
281     }
282     it('can delete the item from the list', function (done) {
283       this.timeout(20 * 1000);
284       deleteFromList(XT.app, workspace.value, done);
285     });
286   };
287 }());