lanes 0.1.9.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (304) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/Rakefile +1 -1
  4. data/client/lanes/Boot.cjsx +3 -3
  5. data/client/lanes/Config.coffee +38 -3
  6. data/client/lanes/access/Extension.coffee +1 -1
  7. data/client/lanes/access/LoginDialog.cjsx +57 -47
  8. data/client/lanes/access/Roles.coffee +2 -2
  9. data/client/lanes/access/User.coffee +4 -3
  10. data/client/lanes/access/screens/user-management/UserManagement.cjsx +12 -11
  11. data/client/lanes/components/calendar/Calendar.cjsx +16 -0
  12. data/client/lanes/components/calendar/index.js +3 -0
  13. data/client/lanes/components/calendar/styles.scss +3 -0
  14. data/client/lanes/components/grid/Body.cjsx +86 -0
  15. data/client/lanes/components/grid/CellStyles.coffee +20 -0
  16. data/client/lanes/components/grid/EditingMixin.cjsx +84 -24
  17. data/client/lanes/components/grid/Editor.cjsx +45 -0
  18. data/client/lanes/components/grid/Grid.cjsx +62 -104
  19. data/client/lanes/components/grid/Header.cjsx +35 -0
  20. data/client/lanes/components/grid/PopOverMixin.cjsx +19 -9
  21. data/client/lanes/components/grid/PopoverEditor.cjsx +7 -1
  22. data/client/lanes/components/grid/RowEditor.cjsx +1 -1
  23. data/client/lanes/components/grid/Selections.cjsx +39 -0
  24. data/client/lanes/components/grid/Toolbar.cjsx +24 -5
  25. data/client/lanes/components/grid/editors.scss +22 -50
  26. data/client/lanes/components/grid/index.js +0 -1
  27. data/client/lanes/components/grid/row-editor.scss +68 -0
  28. data/client/lanes/components/grid/styles.scss +79 -3
  29. data/client/lanes/components/modal/Modal.cjsx +64 -24
  30. data/client/lanes/components/modal/styles.scss +12 -0
  31. data/client/lanes/components/record-finder/Clause.cjsx +11 -4
  32. data/client/lanes/components/record-finder/Dialog.cjsx +23 -24
  33. data/client/lanes/components/record-finder/RecordFinder.cjsx +45 -14
  34. data/client/lanes/components/record-finder/styles.scss +9 -6
  35. data/client/lanes/components/select-field/SelectField.cjsx +108 -53
  36. data/client/lanes/components/select-field/styles.scss +19 -0
  37. data/client/lanes/components/shared/ControlLabel.cjsx +45 -0
  38. data/client/lanes/components/shared/DateTime.cjsx +48 -0
  39. data/client/lanes/components/shared/DisplayValue.cjsx +16 -0
  40. data/client/lanes/components/shared/FieldMixin.cjsx +54 -23
  41. data/client/lanes/components/shared/FieldSet.cjsx +12 -35
  42. data/client/lanes/components/shared/FieldWrapper.cjsx +13 -0
  43. data/client/lanes/components/shared/FormGroup.cjsx +37 -0
  44. data/client/lanes/components/shared/Icon.cjsx +20 -0
  45. data/client/lanes/components/shared/ImageSaver.cjsx +33 -0
  46. data/client/lanes/components/shared/Input.cjsx +19 -0
  47. data/client/lanes/components/shared/InputFieldMixin.cjsx +48 -0
  48. data/client/lanes/components/shared/JobProgress.cjsx +27 -0
  49. data/client/lanes/components/shared/NetworkActivityOverlay.cjsx +58 -0
  50. data/client/lanes/components/shared/NumberInput.cjsx +29 -0
  51. data/client/lanes/components/shared/ResizeSensor.cjsx +11 -0
  52. data/client/lanes/components/shared/ScreenWrapper.cjsx +13 -0
  53. data/client/lanes/components/shared/Throbber.cjsx +3 -0
  54. data/client/lanes/components/shared/ToggleField.cjsx +33 -0
  55. data/client/lanes/components/shared/Tooltip.cjsx +2 -2
  56. data/client/lanes/components/shared/fields.scss +75 -13
  57. data/client/lanes/components/shared/fieldset.scss +3 -5
  58. data/client/lanes/components/shared/image-saver.scss +38 -0
  59. data/client/lanes/components/shared/index.js +2 -0
  60. data/client/lanes/{styles/plugins → components/shared}/overlay.scss +17 -4
  61. data/client/lanes/components/shared/resize-sensor.scss +30 -0
  62. data/client/lanes/components/shared/styles.scss +13 -0
  63. data/client/lanes/components/shared/throbber.scss +53 -0
  64. data/client/lanes/components/toolbar/RemoteChangeSets.cjsx +21 -48
  65. data/client/lanes/components/toolbar/SaveButton.cjsx +24 -0
  66. data/client/lanes/components/toolbar/Toolbar.cjsx +24 -37
  67. data/client/lanes/components/toolbar/changes-notification.scss +10 -6
  68. data/client/lanes/components/toolbar/styles.scss +29 -9
  69. data/client/lanes/extension/Base.coffee +4 -5
  70. data/client/lanes/index.js +0 -1
  71. data/client/lanes/index.scss.erb +10 -1
  72. data/client/lanes/lib/HotReload.coffee +13 -15
  73. data/client/lanes/lib/MakeBaseClass.coffee +6 -1
  74. data/client/lanes/lib/development.coffee +2 -0
  75. data/client/lanes/lib/dom-polyfills.coffee +5 -0
  76. data/client/lanes/lib/dom.coffee +38 -9
  77. data/client/lanes/lib/format.coffee +11 -0
  78. data/client/lanes/lib/index.js.erb +2 -0
  79. data/client/lanes/lib/production.coffee +6 -0
  80. data/client/lanes/lib/utilFunctions.coffee +50 -15
  81. data/client/lanes/models/AssociationMap.coffee +122 -46
  82. data/client/lanes/models/AssociationProxy.coffee +147 -0
  83. data/client/lanes/models/Base.coffee +97 -85
  84. data/client/lanes/models/ChangeMonitor.coffee +7 -3
  85. data/client/lanes/models/ChangeSet.coffee +2 -2
  86. data/client/lanes/models/Collection.coffee +49 -6
  87. data/client/lanes/models/JobStatus.coffee +32 -0
  88. data/client/lanes/models/PubSub.coffee +7 -5
  89. data/client/lanes/models/Query.coffee +115 -34
  90. data/client/lanes/models/ServerCache.coffee +67 -52
  91. data/client/lanes/models/State.coffee +97 -0
  92. data/client/lanes/models/Sync.coffee +18 -13
  93. data/client/lanes/models/SystemSettings.coffee +0 -0
  94. data/client/lanes/models/index.js +1 -0
  95. data/client/lanes/models/mixins/FileSupport.coffee +60 -0
  96. data/client/lanes/models/mixins/HasCodeField.coffee +13 -6
  97. data/client/lanes/models/query/ArrayResult.coffee +188 -0
  98. data/client/lanes/models/query/CollectionResult.coffee +71 -0
  99. data/client/lanes/models/query/Result.coffee +9 -0
  100. data/client/lanes/react/Component.coffee +7 -3
  101. data/client/lanes/react/PubSub.coffee +7 -7
  102. data/client/lanes/react/Root.cjsx +1 -4
  103. data/client/lanes/react/Screen.coffee +1 -0
  104. data/client/lanes/react/TypeValidators.coffee +3 -3
  105. data/client/lanes/react/Viewport.coffee +41 -7
  106. data/client/lanes/react/index.js +0 -1
  107. data/client/lanes/react/mixins/Access.coffee +4 -1
  108. data/client/lanes/react/mixins/Data.coffee +40 -25
  109. data/client/lanes/react/mixins/FieldErrors.coffee +27 -0
  110. data/client/lanes/react/mixins/RelayEditingState.coffee +4 -0
  111. data/client/lanes/react/mixins/Screen.coffee +14 -0
  112. data/client/lanes/react/mixins/Viewport.coffee +9 -3
  113. data/client/lanes/screens/ChangeListener.coffee +3 -3
  114. data/client/lanes/screens/Commands.coffee +14 -7
  115. data/client/lanes/screens/CommonComponents.cjsx +20 -0
  116. data/client/lanes/screens/Definitions.coffee +64 -20
  117. data/client/lanes/screens/SystemSettings.cjsx +56 -0
  118. data/client/lanes/screens/UserPreferences.cjsx +38 -0
  119. data/client/lanes/screens/index.js +3 -0
  120. data/client/lanes/screens/styles.scss +2 -1
  121. data/client/lanes/styles/fonts.scss +1 -0
  122. data/client/lanes/styles/global.scss +2 -1
  123. data/client/lanes/styles/global/flexbox.scss +16 -0
  124. data/client/lanes/styles/global/styles.scss +1 -0
  125. data/client/lanes/styles/mixins/_dropdown.scss +21 -0
  126. data/client/lanes/styles/mixins/_flexbox.scss +394 -0
  127. data/client/lanes/styles/mixins/all.scss +2 -0
  128. data/client/lanes/styles/variables.scss +28 -0
  129. data/client/lanes/testing/BeforeEach.coffee +15 -14
  130. data/client/lanes/testing/Helpers.coffee +14 -5
  131. data/client/lanes/testing/TestObjects.coffee +10 -2
  132. data/client/lanes/testing/index.js +1 -0
  133. data/client/lanes/testing/jasmine-react.js +125 -0
  134. data/client/lanes/vendor/base.js +56049 -74987
  135. data/client/lanes/vendor/calendar.js +17301 -0
  136. data/client/lanes/vendor/calendar.scss +303 -0
  137. data/client/lanes/vendor/commons.js +14990 -15847
  138. data/client/lanes/vendor/development.js +4912 -1952
  139. data/client/lanes/vendor/grid.js +14246 -5551
  140. data/client/lanes/vendor/grid.scss +876 -335
  141. data/client/lanes/vendor/index.js +1 -0
  142. data/client/lanes/vendor/message-bus-ajax.js +44 -0
  143. data/client/lanes/vendor/message-bus.js +414 -0
  144. data/client/lanes/vendor/rw-widgets.eot +0 -0
  145. data/client/lanes/vendor/rw-widgets.svg +18 -0
  146. data/client/lanes/vendor/rw-widgets.ttf +0 -0
  147. data/client/lanes/vendor/rw-widgets.woff +0 -0
  148. data/client/lanes/vendor/toggle.js +345 -0
  149. data/client/lanes/vendor/toggle.scss +138 -0
  150. data/client/lanes/vendor/widgets.js +21245 -6839
  151. data/client/lanes/vendor/widgets.scss +83 -67
  152. data/client/lanes/workspace/Layout.cjsx +18 -8
  153. data/client/lanes/workspace/Modal.cjsx +47 -0
  154. data/client/lanes/workspace/Navbar.cjsx +16 -2
  155. data/client/lanes/workspace/ScreenView.cjsx +10 -3
  156. data/client/lanes/workspace/ScreensMenu.cjsx +23 -7
  157. data/client/lanes/workspace/Tabs.cjsx +55 -0
  158. data/client/lanes/workspace/UIState.coffee +7 -8
  159. data/client/lanes/workspace/index.js +2 -1
  160. data/client/lanes/workspace/mixin.coffee +11 -0
  161. data/client/lanes/workspace/mixins/UIState.coffee +8 -0
  162. data/client/lanes/workspace/mixins/index.js +3 -0
  163. data/client/lanes/workspace/styles.scss +2 -1
  164. data/client/lanes/workspace/styles/header.scss +23 -1
  165. data/client/lanes/workspace/styles/layout.scss +26 -24
  166. data/client/lanes/workspace/styles/screens.scss +0 -4
  167. data/client/lanes/workspace/styles/tabs.scss +3 -10
  168. data/config/routes.rb +10 -4
  169. data/config/screens.rb +25 -0
  170. data/db/migrate/01_create_system_settings.rb +10 -0
  171. data/docs/todo-example-part-1.md +18 -20
  172. data/lanes.gemspec +15 -6
  173. data/lib/lanes.rb +4 -2
  174. data/lib/lanes/access/config/routes.rb +5 -3
  175. data/lib/lanes/access/config/screens.rb +1 -0
  176. data/lib/lanes/access/db/migrate/20140615031600_create_lanes_users.rb +1 -1
  177. data/lib/lanes/access/user.rb +1 -1
  178. data/lib/lanes/api.rb +2 -1
  179. data/lib/lanes/api/controller.rb +32 -71
  180. data/lib/lanes/api/default_routes.rb +10 -8
  181. data/lib/lanes/api/formatted_reply.rb +53 -0
  182. data/lib/lanes/api/handlers/file.rb +26 -0
  183. data/lib/lanes/api/helper_methods.rb +29 -5
  184. data/lib/lanes/api/javascript_processor.rb +36 -17
  185. data/lib/lanes/api/pub_sub.rb +6 -9
  186. data/lib/lanes/api/request_wrapper.rb +1 -2
  187. data/lib/lanes/api/root.rb +11 -43
  188. data/lib/lanes/api/routing.rb +63 -0
  189. data/lib/lanes/api/sprockets_extension.rb +15 -7
  190. data/lib/lanes/api/updates.rb +1 -2
  191. data/lib/lanes/command.rb +0 -1
  192. data/lib/lanes/command/app.rb +6 -5
  193. data/lib/lanes/command/console.rb +1 -0
  194. data/lib/lanes/command/generate.rb +3 -0
  195. data/lib/lanes/command/generate_migration.rb +33 -0
  196. data/lib/lanes/command/generate_model.rb +4 -26
  197. data/lib/lanes/command/migration_support.rb +29 -0
  198. data/lib/lanes/command/update_model.rb +14 -5
  199. data/lib/lanes/concerns/all.rb +2 -0
  200. data/lib/lanes/concerns/api_path.rb +4 -2
  201. data/lib/lanes/concerns/association_extensions.rb +1 -1
  202. data/lib/lanes/concerns/attr_accessor_with_default.rb +3 -1
  203. data/lib/lanes/concerns/code_identifier.rb +1 -1
  204. data/lib/lanes/concerns/image_uploader.rb +42 -0
  205. data/lib/lanes/concerns/pub_sub.rb +0 -1
  206. data/lib/lanes/concerns/queries.rb +2 -2
  207. data/lib/lanes/concerns/set_attribute_data.rb +4 -13
  208. data/lib/lanes/concerns/sorting_expressions.rb +34 -0
  209. data/lib/lanes/configuration.rb +48 -9
  210. data/lib/lanes/extension.rb +16 -7
  211. data/lib/lanes/extension/definition.rb +8 -2
  212. data/lib/lanes/job.rb +78 -0
  213. data/lib/lanes/job/failure_logger.rb +33 -0
  214. data/lib/lanes/model.rb +4 -0
  215. data/lib/lanes/rake_tasks.rb +6 -0
  216. data/lib/lanes/redis.rb +13 -0
  217. data/lib/lanes/screen.rb +34 -18
  218. data/lib/lanes/system_settings.rb +66 -0
  219. data/lib/lanes/version.rb +1 -1
  220. data/lib/lanes/workspace/extension.rb +1 -1
  221. data/npm-build/base.js +10 -3
  222. data/npm-build/calendar.js +6 -0
  223. data/npm-build/development.js +4 -5
  224. data/npm-build/grid.js +3 -5
  225. data/npm-build/package.json +40 -29
  226. data/npm-build/react-toggle.js +5 -0
  227. data/npm-build/react-widgets.js +6 -0
  228. data/npm-build/update-dayz +14 -0
  229. data/npm-build/webpack.config.js +5 -2
  230. data/spec/command-reference-files/initial/Gemfile +1 -1
  231. data/spec/command-reference-files/initial/config/routes.rb +2 -0
  232. data/spec/command-reference-files/initial/lib/appy-app.rb +4 -0
  233. data/spec/command-reference-files/initial/lib/appy-app/extension.rb +2 -0
  234. data/spec/command-reference-files/initial/spec/server/{spec_helpers.rb → spec_helper.rb} +0 -0
  235. data/spec/command-reference-files/model/config/routes.rb +2 -0
  236. data/spec/command-reference-files/model/spec/server/test_test_spec.rb +1 -1
  237. data/spec/command-reference-files/screen/client/appy-app/screens/ready-set-go/ReadySetGo.cjsx +6 -4
  238. data/spec/command-reference-files/screen/client/appy-app/screens/ready-set-go/index.scss +4 -3
  239. data/spec/command-reference-files/screen/config/screens.rb +4 -2
  240. data/spec/command-reference-files/screen/spec/appy-app/screens/ready-set-go/ReadySetGoSpec.coffee +1 -1
  241. data/spec/fixtures/system_settings.yml +1 -0
  242. data/spec/lanes/components/grid/GridSpec.coffee +56 -31
  243. data/spec/lanes/components/grid/RowEditorSpec.coffee +96 -0
  244. data/spec/lanes/components/select-field/SelectFieldSpec.coffee +99 -0
  245. data/spec/lanes/components/shared/NetworkActivityOverlaySpec.coffee +34 -0
  246. data/spec/lanes/models/AssociationMapSpec.coffee +36 -2
  247. data/spec/lanes/models/AssociationProxySpec.coffee +77 -0
  248. data/spec/lanes/models/BaseSpec.coffee +37 -4
  249. data/spec/lanes/models/CollectionSpec.coffee +11 -17
  250. data/spec/lanes/models/PubSubSpec.coffee +1 -1
  251. data/spec/lanes/models/ServerCacheSpec.coffee +65 -0
  252. data/spec/server/api/coffeescript_processor_spec.rb +1 -1
  253. data/spec/server/concerns/pub_sub_spec.rb +9 -10
  254. data/spec/server/concerns/sorting_expressions_spec.rb +34 -0
  255. data/spec/server/configuration_spec.rb +3 -3
  256. data/spec/server/job_spec.rb +54 -0
  257. data/spec/server/spec_helper.rb +0 -5
  258. data/spec/server/system_settings_spec.rb +23 -0
  259. data/templates/client/screens/Screen.cjsx +6 -4
  260. data/templates/client/screens/styles.scss +4 -3
  261. data/templates/config/routes.rb +2 -0
  262. data/templates/config/screen.rb +4 -2
  263. data/templates/lib/namespace.rb +4 -0
  264. data/templates/lib/namespace/extension.rb +2 -0
  265. data/templates/spec/client/Screen.coffee +1 -1
  266. data/templates/spec/server/model_spec.rb +1 -1
  267. data/templates/spec/server/{spec_helpers.rb → spec_helper.rb} +0 -0
  268. data/views/lanes_root_view.erb +70 -0
  269. data/views/specs.erb +2 -2
  270. metadata +207 -68
  271. data/client/images/lanes/dataTables/Sorting icons.psd +0 -0
  272. data/client/images/lanes/dataTables/back_disabled.png +0 -0
  273. data/client/images/lanes/dataTables/back_enabled.png +0 -0
  274. data/client/images/lanes/dataTables/back_enabled_hover.png +0 -0
  275. data/client/images/lanes/dataTables/favicon.ico +0 -0
  276. data/client/images/lanes/dataTables/forward_disabled.png +0 -0
  277. data/client/images/lanes/dataTables/forward_enabled.png +0 -0
  278. data/client/images/lanes/dataTables/forward_enabled_hover.png +0 -0
  279. data/client/images/lanes/dataTables/loading-background.png +0 -0
  280. data/client/images/lanes/dataTables/sort_asc.png +0 -0
  281. data/client/images/lanes/dataTables/sort_asc_disabled.png +0 -0
  282. data/client/images/lanes/dataTables/sort_both.png +0 -0
  283. data/client/images/lanes/dataTables/sort_desc.png +0 -0
  284. data/client/images/lanes/dataTables/sort_desc_disabled.png +0 -0
  285. data/client/lanes/components/shared/Resize.cjsx +0 -152
  286. data/client/lanes/components/shared/TextArea.cjsx +0 -19
  287. data/client/lanes/components/shared/TextField.cjsx +0 -25
  288. data/client/lanes/models/Bootstrap.coffee +0 -5
  289. data/client/lanes/models/QueryResults.coffee +0 -93
  290. data/client/lanes/react/FormBindings.coffee +0 -103
  291. data/client/lanes/react/Router.cjsx +0 -18
  292. data/client/lanes/styles/dataTables.scss +0 -4
  293. data/client/lanes/styles/plugins/all.scss +0 -2
  294. data/client/lanes/styles/plugins/resize-sensor.scss +0 -24
  295. data/client/lanes/vendor/jquery-2.js +0 -9190
  296. data/client/lanes/vendor/jquery.tap.js +0 -401
  297. data/client/lanes/vendor/magicsuggest.js +0 -1565
  298. data/client/lanes/vendor/message-bus.coffee +0 -264
  299. data/client/lanes/workspace/ActiveScreenSwitcher.cjsx +0 -38
  300. data/client/lanes/workspace/styles/toolbar.scss +0 -4
  301. data/lib/lanes/api/eco.js +0 -516
  302. data/lib/lanes/api/sprockets_compressor.rb +0 -39
  303. data/spec/command-reference-files/model/lib/appy-app.rb +0 -11
  304. data/views/index.erb +0 -19
@@ -0,0 +1,99 @@
1
+ #= require lanes/components/select-field
2
+
3
+ Brand = Lanes.Test.defineModel(
4
+ props: {id: 'integer', code: 'string', name: 'string'}
5
+ )
6
+
7
+ BRAND_DATA = {total:3, success:true, message:"Retrieve succeeded", data:[
8
+ {id: 1, code: "GM", name: "General Motors"}
9
+ {id: 2, code: "FORD", name: "Ford Motor Co."}
10
+ {id: 3, code: "DODGE", name: "Chrysler/Dodge"}
11
+ ]}
12
+
13
+ Car = Lanes.Test.defineModel(
14
+ props: {id: 'integer', code: 'string', brand_id: 'integer'}
15
+ session:
16
+ carTypes: 'array'
17
+
18
+ associations:
19
+ brand: {model: Brand}
20
+ )
21
+
22
+ describe "Lanes.Components.SelectField", ->
23
+ beforeEach (done) ->
24
+ @car = new Car
25
+ LT.syncRespondWith(BRAND_DATA)
26
+ @brands = new Brand.Collection
27
+ @brands.ensureLoaded().then(done)
28
+
29
+ it "renders read only", (done) ->
30
+ sf = LT.renderComponent(LC.SelectField,
31
+ props:{model: @car, name: 'brand', labelField: 'name'})
32
+ expect(_.dom(sf).qs('.value').text).toBe('')
33
+ @car.set(brand: @brands.at(0))
34
+ _.defer ->
35
+ expect(_.dom(sf).qs('.value').text).toBe('General Motors')
36
+ done()
37
+
38
+ it "renders as edit", ->
39
+ @car.set(brand: @brands.at(1))
40
+ sf = LT.renderComponent(LC.SelectField, props:{
41
+ model: @car, name: 'brand', labelField: 'name',
42
+ editOnly: true, collection: @brands })
43
+ expect(_.dom(sf).qs('input[type=text]').value).toEqual('Ford Motor Co.')
44
+
45
+ it 'uses a custom data source', ->
46
+ sf = LT.renderComponent(LC.SelectField, props:{
47
+ model: @car, name: 'brand', editOnly: true
48
+ getSelection: -> { label: 'Toyota', id: 4 }
49
+ })
50
+ expect(_.dom(sf).qs('input[type=text]').value).toEqual('Toyota')
51
+
52
+ it 'sets value when changed', ->
53
+ @car.set(brand: @brands.at(1))
54
+ sf = LT.renderComponent(LC.SelectField, props:{
55
+ model: @car, name: 'brand', labelField: 'name', editOnly: true, collection: @brands
56
+ })
57
+ _.dom(sf).qs('.rw-select').click()
58
+ _.dom(sf).qs('li.rw-list-option:last-child').click()
59
+ expect(@car.brand.code).toEqual('DODGE')
60
+
61
+ it 'can select multiple values', ->
62
+ sf = LT.renderComponent(LC.SelectField, props:{
63
+ multi: true, model: @car, name: 'carTypes',
64
+ labelField: 'name', editOnly: true, collection: @brands
65
+ })
66
+ spyOn(@brands, 'fetch').and.callThrough()
67
+ _.dom(sf).qs('.rw-input').focus()
68
+ expect(@brands.fetch).toHaveBeenCalled()
69
+
70
+ expect( _.dom(sf).qsa('.rw-list-option').length ).toEqual(@brands.length)
71
+ _.dom(sf).qs('.rw-list-option:nth-child(1)').click()
72
+ _.dom(sf).qs('.rw-input').focus()
73
+ _.dom(sf).qs('.rw-list-option:nth-child(3)').click()
74
+ expect(_.pluck(@car.carTypes, 'id')).toEqual([1, 3])
75
+
76
+ it 'renders label for multiple values', ->
77
+ @car.carTypes = [ @brands.at(0), @brands.at(2) ]
78
+ sf = LT.renderComponent(LC.SelectField, props:{
79
+ multi: true, model: @car, name: 'carTypes',
80
+ labelField: 'name', collection: @brands
81
+ })
82
+ expect(_.dom(sf).qs('.value').text).toEqual('General Motors and Chrysler/Dodge')
83
+
84
+ it "renders all choices", ->
85
+ sf = LT.renderComponent(LC.SelectField, props:{
86
+ model: @car, name: 'brand', labelField: 'name', editOnly: true, collection: @brands
87
+ })
88
+ _.dom(sf).qs('.rw-select').click()
89
+ labels = _.pluck _.dom(sf).qsa('li.rw-list-option'), 'textContent'
90
+ expect(labels).toEqual(["General Motors", "Ford Motor Co.", "Chrysler/Dodge"])
91
+
92
+ it "can render a blank row", ->
93
+ sf = LT.renderComponent(LC.SelectField, props:{
94
+ includeBlankRow: true, model: @car, name: 'brand',
95
+ labelField: 'name', editOnly: true, collection: @brands
96
+ })
97
+ _.dom(sf).qs('.rw-select').click()
98
+ labels = _.pluck _.dom(sf).qsa('li.rw-list-option'), 'textContent'
99
+ expect(labels).toEqual(["---------", "General Motors", "Ford Motor Co.", "Chrysler/Dodge"])
@@ -0,0 +1,34 @@
1
+ Model = Lanes.Test.defineModel(
2
+ props: {id: 'integer', code: 'string', name: 'string', notes: 'string'}
3
+ )
4
+
5
+ describe "Lanes.Components.NetworkActivityOverlay", ->
6
+
7
+ it "doesn't render unless requesting", ->
8
+ na = LT.renderComponent(LC.NetworkActivityOverlay, props:{message: 'Lookout 4 Bears'})
9
+ expect(_.dom(na).el).toBe(null)
10
+
11
+ it "renders message", ->
12
+ na = LT.renderComponent(LC.NetworkActivityOverlay, props:{
13
+ visible: true, message: 'Lookout 4 Bears'})
14
+ expect(_.dom(na).qs('.message').text).toBe('Lookout 4 Bears')
15
+
16
+ it 'chooses message based on request type', ->
17
+ na = LT.renderComponent(LC.NetworkActivityOverlay, props:{visible:true})
18
+ na.setState(isRequesting: 'GET')
19
+ expect(_.dom(na).qs('.message').text).toBe('Loading…')
20
+ na.setState(isRequesting: 'POST')
21
+ expect(_.dom(na).qs('.message').text).toBe('Saving…')
22
+ na.setState(isRequesting: 'DELETE')
23
+ expect(_.dom(na).qs('.message').text).toBe('Deleting…')
24
+
25
+ it 'briefly displays an error message', (done) ->
26
+ model = new Model
27
+ na = LT.renderComponent(LC.NetworkActivityOverlay, props:{
28
+ errorTimeout: 2, model:model})
29
+ na.setDataState(hasError: true)
30
+ expect(_.dom(na).qs('.message').text).toBe('Error')
31
+ _.delay( ->
32
+ expect(_.dom(na).el).toBe(null)
33
+ done()
34
+ , 3)
@@ -1,4 +1,8 @@
1
+ #=require lanes/access/User
2
+ #=require lanes/access/Roles
3
+
1
4
  describe "Lanes.Models.AssociationMap", ->
5
+
2
6
  class Color
3
7
  constructor: -> super
4
8
  api_path: -> 'test'
@@ -25,7 +29,12 @@ describe "Lanes.Models.AssociationMap", ->
25
29
  colors: { collection: Color, fk: 'model_fk_id' }
26
30
  props: { id: 'integer', foo: 'string' }
27
31
  }, { id: 123 })
32
+
33
+ expect(model.associations.isCreated(model, 'colors')).toBe(false)
34
+
28
35
  color = model.colors.add({ rgb:'#ffffff' })
36
+
37
+ expect(model.associations.isCreated(model, 'colors')).toBe(true)
29
38
  expect(color).toEqual(jasmine.any(Color))
30
39
  expect(color.model_fk_id).toEqual(123)
31
40
 
@@ -44,8 +53,33 @@ describe "Lanes.Models.AssociationMap", ->
44
53
  associations:
45
54
  color:{ model: Color }
46
55
  props: { id: 'integer', foo: 'string', color_id: 'integer' }
47
- }, { id: 123, color: { rgb: 'red' } })
56
+ }, { id: 123, color: {} })
57
+
58
+ expect(model.associations.isCreated(model, 'color')).toBe(false)
48
59
  color = model.color
49
- expect(model.color).toEqual(jasmine.any(Color))
60
+ expect(model.associations.isCreated(model, 'color')).toBe(true)
61
+
50
62
 
63
+ model.set(color: {rgb: 'red'})
51
64
  expect(model.color.rgb).toEqual('red')
65
+
66
+
67
+ it "serializes associations", ->
68
+ modelData = {
69
+ id: 123, title: 'Colors', color: { rgb: 'red' },
70
+ colors: [
71
+ { id: 1, rgb: '123' }
72
+ { id: 2, rgb: '1234' }
73
+ { id: 3, rgb: '12345' }
74
+ ]
75
+ }
76
+ model = LT.makeModel({
77
+ props: { id: 'integer', title: 'string' }
78
+ associations:
79
+ color:{ model: Color }
80
+ colors: { collection: Color, fk: 'model_fk_id' }
81
+ }, modelData)
82
+
83
+ expect(model.serialize()).toEqual(
84
+ modelData
85
+ )
@@ -0,0 +1,77 @@
1
+ describe "Lanes.Models.AssocationProxy", ->
2
+
3
+ class Color
4
+ constructor: -> super
5
+ api_path: -> 'test'
6
+ props: { id: 'integer', model_fk_id: 'integer', rgb: 'string' }
7
+ testFunc: (arg) -> @rgb = arg
8
+ Lanes.Models.Base.extend(Color)
9
+
10
+ beforeEach ->
11
+ @Model = Lanes.Test.defineModel({
12
+ associations:
13
+ color:{ model: Color }
14
+ colors: { collection: Color, fk: 'model_fk_id' }
15
+ props: { id: 'integer', property: 'string' }
16
+ session: { session: 'string' }
17
+ derived:
18
+ dr: { fn: -> 'dr' }
19
+ })
20
+ @Proxy = Lanes.Models.AssocationProxy.construct(@Model)
21
+
22
+ it "proxies the associations with other proxies", ->
23
+ instance = new @Proxy
24
+ expect(instance.color.isProxy).toBe(true)
25
+ expect(instance.color.id).toBeNull()
26
+
27
+ it 'can create a real association and return it if needed', ->
28
+ instance = new @Proxy
29
+ expect(instance.color.isProxy).toBe(true)
30
+ instance.color.rgb = 'foo'
31
+ expect(instance.color.isProxy).toBeUndefined()
32
+ expect(instance.color).toEqual(jasmine.any(Color))
33
+
34
+ it 'can proxy derived properties', ->
35
+ instance = new @Proxy
36
+ expect(instance.dr).toBeNull()
37
+
38
+ it 'raises error when writing to derived properties', ->
39
+ instance = new @Proxy
40
+ expect(->
41
+ instance.dr = 'foo'
42
+ ).toThrowError(TypeError)
43
+
44
+ it 'instantiates and forwards function calls', ->
45
+ instance = new @Proxy
46
+ instance.color.testFunc('1234')
47
+ expect(instance.color).toEqual(jasmine.any(Color))
48
+ expect(instance.color.rgb).toEqual('1234')
49
+
50
+ it 'intercepts dataForSave', ->
51
+ instance = new @Proxy
52
+ instance.color.rgb = 'mycolor'
53
+ expect(instance.dataForSave()).toEqual(color: {rgb: 'mycolor'})
54
+
55
+ it 'provides api_key', ->
56
+ instance = new @Proxy
57
+ expect(instance.api_path()).toEqual('/s')
58
+
59
+ it 'stores events and eventually sets them on model', ->
60
+ spy1 = jasmine.createSpy()
61
+ spy2 = jasmine.createSpy()
62
+ instance = new @Proxy
63
+ instance.color.on('change:rgb', spy1)
64
+ instance.color.once('change:rgb', spy2)
65
+ expect(instance.color.isProxy).toBe(true)
66
+ instance.color.rgb = 'newcolor'
67
+ expect(instance.color.isProxy).toBeUndefined()
68
+
69
+ expect(spy1).toHaveBeenCalled()
70
+ expect(spy2).toHaveBeenCalled()
71
+
72
+ it 'fires onreplace', ->
73
+ spy = jasmine.createSpy()
74
+ instance = new @Proxy
75
+ instance.on('proxyreplace', spy)
76
+ instance.session = 'foo'
77
+ expect(spy).toHaveBeenCalled()
@@ -50,6 +50,7 @@ describe "Lanes.Models.Base", ->
50
50
  fn: -> 'blarg'
51
51
  })
52
52
  model.set( id: 10, foo:'bar', unsaved: 'falsify', color: { rgb: '99FFFF' } )
53
+ expect(model.color.rgb).toEqual('99FFFF')
53
54
  expect(id: 10, foo:'bar', color: { rgb: '99FFFF' }).toEqual( model.dataForSave() )
54
55
  model.foo = 'a value'
55
56
  a = model.changeMonitor.changedAttributes()
@@ -70,7 +71,7 @@ describe "Lanes.Models.Base", ->
70
71
  model.id = 11
71
72
  expect(model.isNew()).toBe(false)
72
73
  Lanes.Models.Sync.perform.calls.reset()
73
- syncSucceedWith({
74
+ LT.syncSucceedWith({
74
75
  foo: 'a new foo value'
75
76
  })
76
77
  model.save().then ->
@@ -83,7 +84,7 @@ describe "Lanes.Models.Base", ->
83
84
  props:
84
85
  { id: 'integer', foo: 'string' }
85
86
  })
86
- syncSucceedWith({
87
+ LT.syncSucceedWith({
87
88
  foo: 'foo value'
88
89
  })
89
90
  model.fetch().then ->
@@ -106,7 +107,7 @@ describe "Lanes.Models.Base", ->
106
107
 
107
108
 
108
109
  it "loads using where", (done) ->
109
- syncSucceedWith([
110
+ LT.syncSucceedWith([
110
111
  { id: 1, title: 'first value' }
111
112
  { id: 2, title: 'second value' }
112
113
  ])
@@ -148,7 +149,39 @@ describe "Lanes.Models.Base", ->
148
149
  color:{ model: Color }
149
150
  props: { id: 'integer', foo: 'string' }
150
151
  }, { id: 1 })
151
- model.withAssociations('color')
152
+ model.withAssociations(['color'])
152
153
  expect(Lanes.Models.Sync.perform).toHaveBeenCalledWith('read', jasmine.any(Object))
153
154
  options = Lanes.Models.Sync.perform.lastOptions()
154
155
  expect(options.include).toEqual(['color'])
156
+
157
+ it "can copy attributes from another model", ->
158
+ Model = LT.defineModel
159
+ props: { id: 'integer', title: 'string' }
160
+ associations:
161
+ color:{ model: Color }
162
+ a = new Model({ id: 23, title: 'Foo', color: { rgb: '123456' } })
163
+ b = new Model()
164
+ b.copyFrom(a)
165
+ expect( b.id ).toEqual(23)
166
+ expect( b.title ).toEqual('Foo')
167
+ expect( b.color.rgb ).toEqual('123456')
168
+
169
+ it 'can copy associations', (done) ->
170
+ Model = LT.defineModel
171
+ props: { id: 'integer', title: 'string' }
172
+ associations:
173
+ color:{ model: Color }
174
+ colors: { collection: Color, fk: 'model_fk_id' }
175
+
176
+ model = new Model(id: 1, title: 'Foo')
177
+ model.color.rgb = 'mycolor'
178
+ color = model.colors.add({ rgb:'1' })
179
+ color = model.colors.add({ rgb:'2' })
180
+ color = model.colors.add({ rgb:'3' })
181
+
182
+ copy = new Model
183
+ copy.copyAssociationsFrom(model, ['color', 'colors']).then ->
184
+ expect(copy.color.rgb).toEqual('mycolor')
185
+ expect(copy.colors.length).toEqual(3)
186
+ expect(copy.colors.pluck('rgb')).toEqual(['1', '2', '3'])
187
+ done()
@@ -5,14 +5,14 @@ describe "Lanes.Models.Collection", ->
5
5
  Model = Lanes.Test.defineModel
6
6
  props: { id: 'integer', title: 'string' }
7
7
 
8
- syncSucceedWith([
8
+ LT.syncSucceedWith([
9
9
  { id: 1, title: 'first value' }
10
10
  { id: 2, title: 'second value' }
11
11
  ])
12
12
  collection = Model.where(name: 'foo')
13
- expect(collection.requestInProgress).toBe(true)
13
+ expect(collection.requestInProgress).toBeDefined()
14
14
  collection.whenLoaded ->
15
- expect( collection.isLoaded() ).toEqual( true )
15
+ expect( collection.isLoaded() ).toBe(true)
16
16
  done()
17
17
 
18
18
  it "triggers length when changed", ->
@@ -31,18 +31,12 @@ describe "Lanes.Models.Collection", ->
31
31
  collection.reset([{ id:11, title: 'last'}])
32
32
  expect(spy).toHaveBeenCalled()
33
33
 
34
- it "reads from caches", (done) ->
35
- model = Lanes.Test.makeModel({
36
- cacheDuration: [1, 'day']
37
- api_path: 'model-cache'
34
+ it 'prevents duplicates when copying from', ->
35
+ Model = Lanes.Test.defineModel
38
36
  props: { id: 'integer', title: 'string' }
39
- }, {id: 1})
40
- Lanes.Models.ServerCache.CACHE["/model-cache"] = [
41
- { id: 1, title: 'first value' }
42
- { id: 2, title: 'second value' }
43
- ]
44
- collection = new model.constructor.Collection
45
- collection.fetch().then ->
46
- expect(collection.length).toEqual(2)
47
- expect(collection.first().title).toEqual('first value')
48
- done()
37
+ collection = new Model.Collection
38
+ a = collection.add({ id: 1, title: 'first' })
39
+ b = collection.add({ id: 2, title: 'second' })
40
+ c = collection.add({ id: 3, title: 'third' })
41
+ a.copyFrom(c)
42
+ expect(collection.pluck('id')).toEqual([2, 3, 1])
@@ -8,7 +8,7 @@ describe "Lanes.Model.PubSub", ->
8
8
  }, { id: 1, foo: 'bar' })
9
9
  component = Lanes.Test.makeComponent({}, model: model)
10
10
  expect(Lanes.Models.PubSub.add).toHaveBeenCalledWith(model)
11
- component.wrapper.destroy()
11
+ component.data.destroy()
12
12
  expect(Lanes.Models.PubSub.remove).toHaveBeenCalledWith(model)
13
13
 
14
14
  it "can retrieve a model after checkin", ->
@@ -0,0 +1,65 @@
1
+ describe "Lanes.Models.ServerCache", ->
2
+
3
+ it "caches models", (done) ->
4
+ record = LT.makeModel({
5
+ cacheDuration: [5, 'seconds']
6
+ props: { id: 'integer', name: 'string' }
7
+ }, { id: 123 })
8
+
9
+ LT.syncSucceedWith(name: 'bob')
10
+
11
+ Lanes.Models.ServerCache.fetchRecord(record).then ->
12
+ expect(Lanes.Models.Sync.perform).toHaveBeenCalledWith('read', jasmine.any(Object))
13
+ expect(record.name).toEqual('bob')
14
+
15
+ Lanes.Models.Sync.perform.calls.reset()
16
+ record.clear()
17
+ record.id = 123
18
+ expect(record.name).toBeUndefined()
19
+ Lanes.Models.ServerCache.fetchRecord(record).then ->
20
+ expect(Lanes.Models.Sync.perform).not.toHaveBeenCalled()
21
+ expect(record.name).toEqual('bob')
22
+ done()
23
+
24
+
25
+ it "caches collections", (done) ->
26
+ record = LT.makeModel({
27
+ cacheDuration: [5, 'seconds']
28
+ props: { id: 'integer', name: 'string' }
29
+ }, { id: 123 })
30
+
31
+ collection = Lanes.Test.makeCollection
32
+ cacheDuration: [5, 'milliseconds']
33
+ model: LT.defineModel
34
+ props: { id: 'integer', name: 'string' }
35
+
36
+ LT.syncSucceedWith([{id:1, name: 'bob'}, {id:2, name: 'joe'}])
37
+
38
+ Lanes.Models.ServerCache.fetchCollection(collection).then ->
39
+ expect(Lanes.Models.Sync.perform).toHaveBeenCalledWith('read', jasmine.any(Object))
40
+ expect(collection.length).toEqual(3)
41
+ Lanes.Models.Sync.perform.calls.reset()
42
+ Lanes.Models.ServerCache.fetchCollection(collection).then ->
43
+ expect(Lanes.Models.Sync.perform).not.toHaveBeenCalled()
44
+ done()
45
+
46
+ it "expires the cache", (done) ->
47
+ record = LT.makeModel({
48
+ cacheDuration: [5, 'milliseconds']
49
+ props: { id: 'integer', name: 'string' }
50
+ }, { id: 123 })
51
+
52
+ LT.syncSucceedWith(name: 'bob')
53
+
54
+ Lanes.Models.ServerCache.fetchRecord(record).then ->
55
+ expect(Lanes.Models.Sync.perform).toHaveBeenCalled()
56
+ Lanes.Models.Sync.perform.calls.reset()
57
+
58
+ Lanes.Models.ServerCache.fetchRecord(record).then ->
59
+ expect(Lanes.Models.Sync.perform).not.toHaveBeenCalled()
60
+
61
+ _.delay( ->
62
+ Lanes.Models.ServerCache.fetchRecord(record).then ->
63
+ expect(Lanes.Models.Sync.perform).toHaveBeenCalled()
64
+ done()
65
+ , 10)