open_conference_ware 1.0.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.autotest +14 -0
  3. data/.coveralls.yml +1 -0
  4. data/.gitignore +21 -0
  5. data/.rspec +1 -0
  6. data/.travis.yml +18 -0
  7. data/CHANGES.md +280 -0
  8. data/CONTRIBUTORS.markdown +17 -0
  9. data/Gemfile +21 -0
  10. data/Gemfile.lock +296 -0
  11. data/Guardfile +24 -0
  12. data/LICENSE.txt +23 -0
  13. data/README.markdown +173 -0
  14. data/Rakefile +27 -0
  15. data/TODO.txt +22 -0
  16. data/app/assets/images/open_conference_ware/accept.png +0 -0
  17. data/app/assets/images/open_conference_ware/arrow-left.png +0 -0
  18. data/app/assets/images/open_conference_ware/arrow-out.png +0 -0
  19. data/app/assets/images/open_conference_ware/arrow-right.png +0 -0
  20. data/app/assets/images/open_conference_ware/arrow-up.png +0 -0
  21. data/app/assets/images/open_conference_ware/arrow_refresh.png +0 -0
  22. data/app/assets/images/open_conference_ware/comment.png +0 -0
  23. data/app/assets/images/open_conference_ware/comment_edit.png +0 -0
  24. data/app/assets/images/open_conference_ware/delete.png +0 -0
  25. data/app/assets/images/open_conference_ware/disk.png +0 -0
  26. data/app/assets/images/open_conference_ware/document-blank.png +0 -0
  27. data/app/assets/images/open_conference_ware/document.png +0 -0
  28. data/app/assets/images/open_conference_ware/edit.png +0 -0
  29. data/app/assets/images/open_conference_ware/extras/001_01.png +0 -0
  30. data/app/assets/images/open_conference_ware/extras/001_05.png +0 -0
  31. data/app/assets/images/open_conference_ware/extras/001_05x16.png +0 -0
  32. data/app/assets/images/open_conference_ware/extras/001_21.png +0 -0
  33. data/app/assets/images/open_conference_ware/extras/001_23.png +0 -0
  34. data/app/assets/images/open_conference_ware/extras/001_24.png +0 -0
  35. data/app/assets/images/open_conference_ware/extras/001_29.png +0 -0
  36. data/app/assets/images/open_conference_ware/extras/01.png +0 -0
  37. data/app/assets/images/open_conference_ware/extras/05.png +0 -0
  38. data/app/assets/images/open_conference_ware/extras/06.png +0 -0
  39. data/app/assets/images/open_conference_ware/extras/1.png +0 -0
  40. data/app/assets/images/open_conference_ware/extras/14.png +0 -0
  41. data/app/assets/images/open_conference_ware/extras/16.png +0 -0
  42. data/app/assets/images/open_conference_ware/extras/2.png +0 -0
  43. data/app/assets/images/open_conference_ware/extras/21.png +0 -0
  44. data/app/assets/images/open_conference_ware/extras/28.png +0 -0
  45. data/app/assets/images/open_conference_ware/extras/3.png +0 -0
  46. data/app/assets/images/open_conference_ware/extras/31.png +0 -0
  47. data/app/assets/images/open_conference_ware/extras/32.png +0 -0
  48. data/app/assets/images/open_conference_ware/extras/35.png +0 -0
  49. data/app/assets/images/open_conference_ware/extras/36.png +0 -0
  50. data/app/assets/images/open_conference_ware/extras/4.png +0 -0
  51. data/app/assets/images/open_conference_ware/extras/44.png +0 -0
  52. data/app/assets/images/open_conference_ware/extras/45.png +0 -0
  53. data/app/assets/images/open_conference_ware/extras/46.png +0 -0
  54. data/app/assets/images/open_conference_ware/extras/cancel.png +0 -0
  55. data/app/assets/images/open_conference_ware/extras/cancelx16.png +0 -0
  56. data/app/assets/images/open_conference_ware/extras/icon.trashcan.gif +0 -0
  57. data/app/assets/images/open_conference_ware/favorite.png +0 -0
  58. data/app/assets/images/open_conference_ware/feed.png +0 -0
  59. data/app/assets/images/open_conference_ware/heart.png +0 -0
  60. data/app/assets/images/open_conference_ware/house.png +0 -0
  61. data/app/assets/images/open_conference_ware/ignite_portland_header3_clear.png +0 -0
  62. data/app/assets/images/open_conference_ware/ignite_portland_header3_clear_cropped.png +0 -0
  63. data/app/assets/images/open_conference_ware/jquery.wysiwyg.gif +0 -0
  64. data/app/assets/images/open_conference_ware/lock.png +0 -0
  65. data/app/assets/images/open_conference_ware/new_window.gif +0 -0
  66. data/app/assets/images/open_conference_ware/openid-icon.gif +0 -0
  67. data/app/assets/images/open_conference_ware/pencil.png +0 -0
  68. data/app/assets/images/open_conference_ware/plus.png +0 -0
  69. data/app/assets/images/open_conference_ware/quote.png +0 -0
  70. data/app/assets/images/open_conference_ware/rails.png +0 -0
  71. data/app/assets/images/open_conference_ware/redx.png +0 -0
  72. data/app/assets/images/open_conference_ware/reset-fff.png +0 -0
  73. data/app/assets/images/open_conference_ware/spinner-big.gif +0 -0
  74. data/app/assets/images/open_conference_ware/spinner.gif +0 -0
  75. data/app/assets/images/open_conference_ware/star.png +0 -0
  76. data/app/assets/images/open_conference_ware/tag_blue.png +0 -0
  77. data/app/assets/images/open_conference_ware/time.png +0 -0
  78. data/app/assets/javascripts/open_conference_ware/application.js +18 -0
  79. data/app/assets/javascripts/open_conference_ware/base.js +15 -0
  80. data/app/assets/javascripts/open_conference_ware/favorites.js +76 -0
  81. data/app/assets/javascripts/open_conference_ware/ie.js +2 -0
  82. data/app/assets/javascripts/open_conference_ware/menu.js +47 -0
  83. data/app/assets/javascripts/open_conference_ware/persona.js +10 -0
  84. data/app/assets/javascripts/open_conference_ware/proposals.js +208 -0
  85. data/app/assets/javascripts/open_conference_ware/schedule.js +20 -0
  86. data/app/assets/javascripts/open_conference_ware/spinner.js +11 -0
  87. data/app/assets/javascripts/open_conference_ware/util.js +23 -0
  88. data/app/assets/stylesheets/open_conference_ware/application.css +8 -0
  89. data/app/assets/stylesheets/open_conference_ware/common.css.scss +491 -0
  90. data/app/assets/stylesheets/open_conference_ware/custom.css.scss +1033 -0
  91. data/app/assets/stylesheets/open_conference_ware/scaffold.css +54 -0
  92. data/app/controllers/open_conference_ware/application_controller.rb +488 -0
  93. data/app/controllers/open_conference_ware/authentications_controller.rb +42 -0
  94. data/app/controllers/open_conference_ware/comments_controller.rb +86 -0
  95. data/app/controllers/open_conference_ware/events_controller.rb +36 -0
  96. data/app/controllers/open_conference_ware/manage/events_controller.rb +157 -0
  97. data/app/controllers/open_conference_ware/manage/snippets_controller.rb +110 -0
  98. data/app/controllers/open_conference_ware/proposals_controller.rb +521 -0
  99. data/app/controllers/open_conference_ware/rooms_controller.rb +130 -0
  100. data/app/controllers/open_conference_ware/schedule_items_controller.rb +128 -0
  101. data/app/controllers/open_conference_ware/selector_votes_controller.rb +71 -0
  102. data/app/controllers/open_conference_ware/session_types_controller.rb +123 -0
  103. data/app/controllers/open_conference_ware/tracks_controller.rb +118 -0
  104. data/app/controllers/open_conference_ware/user_favorites_controller.rb +68 -0
  105. data/app/controllers/open_conference_ware/users_controller.rb +120 -0
  106. data/app/helpers/open_conference_ware/application_helper.rb +167 -0
  107. data/app/helpers/open_conference_ware/authentications_helper.rb +25 -0
  108. data/app/helpers/open_conference_ware/cache_if_helper.rb +13 -0
  109. data/app/helpers/open_conference_ware/display_link_to_helper.rb +25 -0
  110. data/app/helpers/open_conference_ware/display_textile_for_helper.rb +11 -0
  111. data/app/helpers/open_conference_ware/field_annotation_helper.rb +11 -0
  112. data/app/helpers/open_conference_ware/focus_helper.rb +16 -0
  113. data/app/helpers/open_conference_ware/localcss_helper.rb +10 -0
  114. data/app/helpers/open_conference_ware/page_title_helper.rb +17 -0
  115. data/app/helpers/open_conference_ware/proposals_helper.rb +54 -0
  116. data/app/helpers/open_conference_ware/rooms_helper.rb +5 -0
  117. data/app/helpers/open_conference_ware/scroll_to_helper.rb +15 -0
  118. data/app/helpers/open_conference_ware/session_types_helper.rb +5 -0
  119. data/app/helpers/open_conference_ware/snippets_helper.rb +36 -0
  120. data/app/helpers/open_conference_ware/time_range_helper.rb +198 -0
  121. data/app/helpers/open_conference_ware/tracks_helper.rb +9 -0
  122. data/app/helpers/open_conference_ware/user_favorites_helper.rb +15 -0
  123. data/app/mailers/open_conference_ware/speaker_mailer.rb +51 -0
  124. data/app/mixins/open_conference_ware/breadcrumbs_mixin.rb +76 -0
  125. data/app/mixins/open_conference_ware/cache_lookups_mixin.rb +128 -0
  126. data/app/mixins/open_conference_ware/faux_routes_mixin.rb +83 -0
  127. data/app/mixins/open_conference_ware/normalize_url_mixin.rb +40 -0
  128. data/app/mixins/open_conference_ware/public_attributes_mixin.rb +62 -0
  129. data/app/mixins/open_conference_ware/schedule_overlaps_mixin.rb +12 -0
  130. data/app/mixins/open_conference_ware/settings_checkers_mixin.rb +50 -0
  131. data/app/mixins/open_conference_ware/simple_slug_mixin.rb +26 -0
  132. data/app/models/open_conference_ware/authentication.rb +47 -0
  133. data/app/models/open_conference_ware/base.rb +6 -0
  134. data/app/models/open_conference_ware/comment.rb +26 -0
  135. data/app/models/open_conference_ware/event.rb +227 -0
  136. data/app/models/open_conference_ware/proposal.rb +625 -0
  137. data/app/models/open_conference_ware/room.rb +42 -0
  138. data/app/models/open_conference_ware/schedule.rb +216 -0
  139. data/app/models/open_conference_ware/schedule_item.rb +55 -0
  140. data/app/models/open_conference_ware/selector_vote.rb +22 -0
  141. data/app/models/open_conference_ware/session_type.rb +33 -0
  142. data/app/models/open_conference_ware/snippet.rb +58 -0
  143. data/app/models/open_conference_ware/track.rb +51 -0
  144. data/app/models/open_conference_ware/user.rb +262 -0
  145. data/app/models/open_conference_ware/user_favorite.rb +42 -0
  146. data/app/observers/open_conference_ware/cache_watcher.rb +42 -0
  147. data/app/views/layouts/open_conference_ware/_header.html.erb +106 -0
  148. data/app/views/layouts/open_conference_ware/application.atom.erb +1 -0
  149. data/app/views/layouts/open_conference_ware/application.html.erb +126 -0
  150. data/app/views/layouts/open_conference_ware/scaffold.html.erb +21 -0
  151. data/app/views/open_conference_ware/404.html.erb +8 -0
  152. data/app/views/open_conference_ware/422.html.erb +11 -0
  153. data/app/views/open_conference_ware/500.html.erb +11 -0
  154. data/app/views/open_conference_ware/_email_link.html.erb +24 -0
  155. data/app/views/open_conference_ware/_google_analytics.html.erb +9 -0
  156. data/app/views/open_conference_ware/authentications/_developer.html.erb +7 -0
  157. data/app/views/open_conference_ware/authentications/_openid.html.erb +11 -0
  158. data/app/views/open_conference_ware/authentications/_persona.html.erb +17 -0
  159. data/app/views/open_conference_ware/authentications/sign_in.html.erb +12 -0
  160. data/app/views/open_conference_ware/comments/_list.html.erb +31 -0
  161. data/app/views/open_conference_ware/comments/index.atom.builder +25 -0
  162. data/app/views/open_conference_ware/comments/index.html.erb +3 -0
  163. data/app/views/open_conference_ware/events/index.html.erb +9 -0
  164. data/app/views/open_conference_ware/events/show.html.erb +0 -0
  165. data/app/views/open_conference_ware/events/speakers.html.erb +7 -0
  166. data/app/views/open_conference_ware/manage/events/_form.html.erb +107 -0
  167. data/app/views/open_conference_ware/manage/events/edit.html.erb +3 -0
  168. data/app/views/open_conference_ware/manage/events/index.html.erb +41 -0
  169. data/app/views/open_conference_ware/manage/events/new.html.erb +3 -0
  170. data/app/views/open_conference_ware/manage/events/proposals.html.erb +133 -0
  171. data/app/views/open_conference_ware/manage/events/show.html.erb +181 -0
  172. data/app/views/open_conference_ware/manage/snippets/_form.html.erb +30 -0
  173. data/app/views/open_conference_ware/manage/snippets/edit.html.erb +3 -0
  174. data/app/views/open_conference_ware/manage/snippets/index.html.erb +34 -0
  175. data/app/views/open_conference_ware/manage/snippets/new.html.erb +3 -0
  176. data/app/views/open_conference_ware/manage/snippets/show.html.erb +32 -0
  177. data/app/views/open_conference_ware/proposals/_admin_controls.html.erb +33 -0
  178. data/app/views/open_conference_ware/proposals/_form.html.erb +381 -0
  179. data/app/views/open_conference_ware/proposals/_list.html.erb +164 -0
  180. data/app/views/open_conference_ware/proposals/_list_concise.html.erb +93 -0
  181. data/app/views/open_conference_ware/proposals/_manage_speakers.html.erb +30 -0
  182. data/app/views/open_conference_ware/proposals/_room_control.html.erb +21 -0
  183. data/app/views/open_conference_ware/proposals/_schedule_block.html.erb +8 -0
  184. data/app/views/open_conference_ware/proposals/_schedule_control.html.erb +13 -0
  185. data/app/views/open_conference_ware/proposals/_search_speakers.html.erb +20 -0
  186. data/app/views/open_conference_ware/proposals/_sub_list.html.erb +65 -0
  187. data/app/views/open_conference_ware/proposals/_sub_list_for_kind.html.erb +31 -0
  188. data/app/views/open_conference_ware/proposals/_transition_control.html.erb +16 -0
  189. data/app/views/open_conference_ware/proposals/create.html.erb +29 -0
  190. data/app/views/open_conference_ware/proposals/edit.html.erb +7 -0
  191. data/app/views/open_conference_ware/proposals/index.atom.builder +63 -0
  192. data/app/views/open_conference_ware/proposals/index.html.erb +46 -0
  193. data/app/views/open_conference_ware/proposals/new.html.erb +3 -0
  194. data/app/views/open_conference_ware/proposals/schedule.html.erb +108 -0
  195. data/app/views/open_conference_ware/proposals/sessions_index_terse.html.erb +22 -0
  196. data/app/views/open_conference_ware/proposals/show.html.erb +316 -0
  197. data/app/views/open_conference_ware/proposals/stats.html.erb +72 -0
  198. data/app/views/open_conference_ware/rooms/_form.html.erb +36 -0
  199. data/app/views/open_conference_ware/rooms/edit.html.erb +3 -0
  200. data/app/views/open_conference_ware/rooms/index.html.erb +13 -0
  201. data/app/views/open_conference_ware/rooms/new.html.erb +3 -0
  202. data/app/views/open_conference_ware/rooms/show.html.erb +27 -0
  203. data/app/views/open_conference_ware/schedule_items/_form.html.erb +35 -0
  204. data/app/views/open_conference_ware/schedule_items/edit.html.erb +3 -0
  205. data/app/views/open_conference_ware/schedule_items/index.html.erb +31 -0
  206. data/app/views/open_conference_ware/schedule_items/new.html.erb +3 -0
  207. data/app/views/open_conference_ware/schedule_items/show.html.erb +30 -0
  208. data/app/views/open_conference_ware/selector_votes/index.html.erb +81 -0
  209. data/app/views/open_conference_ware/session_types/_form.html.erb +23 -0
  210. data/app/views/open_conference_ware/session_types/edit.html.erb +3 -0
  211. data/app/views/open_conference_ware/session_types/index.html.erb +18 -0
  212. data/app/views/open_conference_ware/session_types/new.html.erb +3 -0
  213. data/app/views/open_conference_ware/session_types/show.html.erb +14 -0
  214. data/app/views/open_conference_ware/speaker_mailer/speaker_email.text.erb +14 -0
  215. data/app/views/open_conference_ware/tracks/_colors.css.erb +7 -0
  216. data/app/views/open_conference_ware/tracks/_form.html.erb +36 -0
  217. data/app/views/open_conference_ware/tracks/edit.html.erb +3 -0
  218. data/app/views/open_conference_ware/tracks/index.html.erb +25 -0
  219. data/app/views/open_conference_ware/tracks/new.html.erb +3 -0
  220. data/app/views/open_conference_ware/tracks/show.html.erb +19 -0
  221. data/app/views/open_conference_ware/user_favorites/index.html.erb +12 -0
  222. data/app/views/open_conference_ware/users/_account_box.html.erb +10 -0
  223. data/app/views/open_conference_ware/users/_block.html.erb +80 -0
  224. data/app/views/open_conference_ware/users/_list.html.erb +17 -0
  225. data/app/views/open_conference_ware/users/edit.html.erb +73 -0
  226. data/app/views/open_conference_ware/users/index.html.erb +28 -0
  227. data/app/views/open_conference_ware/users/new.html.erb +7 -0
  228. data/app/views/open_conference_ware/users/proposals.html.erb +24 -0
  229. data/app/views/open_conference_ware/users/show.html.erb +5 -0
  230. data/app/views/open_conference_ware/welcome/test.html.haml +8 -0
  231. data/bin/rails +8 -0
  232. data/ci/Gemfile.ci +13 -0
  233. data/ci/copy_database_config.rb +6 -0
  234. data/ci/database.mysql.yml +5 -0
  235. data/ci/database.postgresql.yml +4 -0
  236. data/ci/database.sqlite.yml +5 -0
  237. data/config/initializers/acts_as_taggable_on.rb +2 -0
  238. data/config/initializers/date_time_formats.rb +9 -0
  239. data/config/routes.rb +74 -0
  240. data/db/migrate/20131203235128_create_open_conference_ware_authentications.rb +14 -0
  241. data/db/migrate/20131203235216_create_open_conference_ware_comments.rb +14 -0
  242. data/db/migrate/20131203235418_create_open_conference_ware_events.rb +27 -0
  243. data/db/migrate/20131203235524_create_open_conference_ware_proposals.rb +36 -0
  244. data/db/migrate/20131203235723_create_open_conference_ware_rooms.rb +20 -0
  245. data/db/migrate/20131203235831_create_open_conference_ware_schedule_items.rb +18 -0
  246. data/db/migrate/20131203235910_create_open_conference_ware_selector_votes.rb +10 -0
  247. data/db/migrate/20131203235934_create_open_conference_ware_session_types.rb +14 -0
  248. data/db/migrate/20131204000008_create_open_conference_ware_snippets.rb +15 -0
  249. data/db/migrate/20131204000048_create_open_conference_ware_taggings.rb +16 -0
  250. data/db/migrate/20131204000129_create_open_conference_ware_tags.rb +7 -0
  251. data/db/migrate/20131204000147_create_open_conference_ware_tracks.rb +15 -0
  252. data/db/migrate/20131204000230_create_open_conference_ware_user_favorites.rb +10 -0
  253. data/db/migrate/20131204000251_create_open_conference_ware_users.rb +24 -0
  254. data/db/migrate/20131204000355_create_proposals_users_join_table.rb +8 -0
  255. data/db/seeds.rb +31 -0
  256. data/features/comment_create.feature +9 -0
  257. data/features/comment_destroy.feature +22 -0
  258. data/features/comment_new.feature +22 -0
  259. data/features/step_definitions/authentication_steps.rb +22 -0
  260. data/features/step_definitions/comment_create_steps.rb +5 -0
  261. data/features/step_definitions/comment_destroy_steps.rb +13 -0
  262. data/features/step_definitions/comment_new_steps.rb +41 -0
  263. data/features/step_definitions/notification_steps.rb +3 -0
  264. data/features/step_definitions/web_steps.rb +263 -0
  265. data/features/support/env.rb +58 -0
  266. data/features/support/helpers.rb +17 -0
  267. data/features/support/paths.rb +35 -0
  268. data/gem-public_cert.pem +22 -0
  269. data/lib/defer_proxy.rb +127 -0
  270. data/lib/ext/color_rgb_serializer_fix.rb +18 -0
  271. data/lib/ext/string_possessiveize.rb +7 -0
  272. data/lib/ext/vpim_icalendar_extra_properties.rb +25 -0
  273. data/lib/generators/open_conference_ware/install/USAGE +12 -0
  274. data/lib/generators/open_conference_ware/install/install_generator.rb +35 -0
  275. data/lib/generators/open_conference_ware/install/templates/config_initializer.rb +110 -0
  276. data/lib/generators/open_conference_ware/install/templates/omniauth_initializer.rb +33 -0
  277. data/lib/open_conference_ware.rb +156 -0
  278. data/lib/open_conference_ware/dependencies.rb +38 -0
  279. data/lib/open_conference_ware/engine.rb +38 -0
  280. data/lib/open_conference_ware/omni_auth_builder.rb +8 -0
  281. data/lib/open_conference_ware/version.rb +3 -0
  282. data/lib/rwikibot_page_drone.rb +70 -0
  283. data/lib/tasks/.gitkeep +0 -0
  284. data/lib/tasks/audio.rake +53 -0
  285. data/lib/tasks/cucumber.rake +65 -0
  286. data/lib/tasks/export.rake +170 -0
  287. data/lib/tasks/mediawiki.rake +96 -0
  288. data/lib/tasks/open_conference_ware_tasks.rake +16 -0
  289. data/lib/tasks/schedule.rake +103 -0
  290. data/lib/tasks/snippets.rake +23 -0
  291. data/open_conference_ware.gemspec +67 -0
  292. data/original_graphics/favorite.psd +0 -0
  293. data/spec/controllers/open_conference_ware/application_controller_spec.rb +309 -0
  294. data/spec/controllers/open_conference_ware/authentications_controller_spec.rb +87 -0
  295. data/spec/controllers/open_conference_ware/comments_controller_spec.rb +146 -0
  296. data/spec/controllers/open_conference_ware/events_controller_spec.rb +102 -0
  297. data/spec/controllers/open_conference_ware/manage_events_controller_spec.rb +103 -0
  298. data/spec/controllers/open_conference_ware/proposals_controller_spec.rb +1273 -0
  299. data/spec/controllers/open_conference_ware/rooms_controller_spec.rb +190 -0
  300. data/spec/controllers/open_conference_ware/selector_votes_controller_spec.rb +263 -0
  301. data/spec/controllers/open_conference_ware/session_types_controller_spec.rb +190 -0
  302. data/spec/controllers/open_conference_ware/sessions_routing_spec.rb +13 -0
  303. data/spec/controllers/open_conference_ware/tracks_controller_spec.rb +190 -0
  304. data/spec/controllers/open_conference_ware/user_favorites_controller_spec.rb +127 -0
  305. data/spec/controllers/open_conference_ware/users_controller_spec.rb +166 -0
  306. data/spec/dummy/README.rdoc +28 -0
  307. data/spec/dummy/Rakefile +6 -0
  308. data/spec/dummy/app/assets/images/.keep +0 -0
  309. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  310. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  311. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  312. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  313. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  314. data/spec/dummy/app/mailers/.keep +0 -0
  315. data/spec/dummy/app/models/.keep +0 -0
  316. data/spec/dummy/app/models/concerns/.keep +0 -0
  317. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  318. data/spec/dummy/bin/bundle +3 -0
  319. data/spec/dummy/bin/rails +4 -0
  320. data/spec/dummy/bin/rake +4 -0
  321. data/spec/dummy/config.ru +4 -0
  322. data/spec/dummy/config/application.rb +30 -0
  323. data/spec/dummy/config/boot.rb +5 -0
  324. data/spec/dummy/config/database.yml +25 -0
  325. data/spec/dummy/config/environment.rb +5 -0
  326. data/spec/dummy/config/environments/development.rb +29 -0
  327. data/spec/dummy/config/environments/production.rb +80 -0
  328. data/spec/dummy/config/environments/test.rb +36 -0
  329. data/spec/dummy/config/initializers/01_open_conference_ware.rb +110 -0
  330. data/spec/dummy/config/initializers/02_omniauth.rb +17 -0
  331. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  332. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  333. data/spec/dummy/config/initializers/inflections.rb +16 -0
  334. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  335. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  336. data/spec/dummy/config/initializers/session_store.rb +3 -0
  337. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  338. data/spec/dummy/config/locales/en.yml +23 -0
  339. data/spec/dummy/config/routes.rb +3 -0
  340. data/spec/dummy/db/schema.rb +219 -0
  341. data/spec/dummy/db/seeds.rb +10 -0
  342. data/spec/dummy/lib/assets/.keep +0 -0
  343. data/spec/dummy/log/.keep +0 -0
  344. data/spec/dummy/public/404.html +58 -0
  345. data/spec/dummy/public/422.html +58 -0
  346. data/spec/dummy/public/500.html +57 -0
  347. data/spec/dummy/public/favicon.ico +0 -0
  348. data/spec/factories/authentication_factory.rb +16 -0
  349. data/spec/factories/common_factory.rb +4 -0
  350. data/spec/factories/event_factory.rb +26 -0
  351. data/spec/factories/proposal_factory.rb +57 -0
  352. data/spec/factories/proposal_user_factory.rb +7 -0
  353. data/spec/factories/room_factory.rb +11 -0
  354. data/spec/factories/schedule_item_factory.rb +13 -0
  355. data/spec/factories/session_types_factory.rb +10 -0
  356. data/spec/factories/snippet_factory.rb +7 -0
  357. data/spec/factories/track_factory.rb +12 -0
  358. data/spec/factories/user_factory.rb +22 -0
  359. data/spec/factories/user_favorite_factory.rb +7 -0
  360. data/spec/features/sign_in_spec.rb +11 -0
  361. data/spec/features/snippets_spec.rb +38 -0
  362. data/spec/fixtures/open_conference_ware_comments.yml +27 -0
  363. data/spec/fixtures/open_conference_ware_events.yml +56 -0
  364. data/spec/fixtures/open_conference_ware_proposals.yml +256 -0
  365. data/spec/fixtures/open_conference_ware_rooms.yml +13 -0
  366. data/spec/fixtures/open_conference_ware_schedule_items.yml +23 -0
  367. data/spec/fixtures/open_conference_ware_session_types.yml +19 -0
  368. data/spec/fixtures/open_conference_ware_snippets.yml +62 -0
  369. data/spec/fixtures/open_conference_ware_tracks.yml +65 -0
  370. data/spec/fixtures/open_conference_ware_user_favorites.yml +7 -0
  371. data/spec/fixtures/open_conference_ware_users.yml +87 -0
  372. data/spec/helpers/open_conference_ware/application_helper_spec.rb +58 -0
  373. data/spec/helpers/open_conference_ware/authentications_helper_spec.rb +15 -0
  374. data/spec/helpers/open_conference_ware/display_link_to_helper_spec.rb +52 -0
  375. data/spec/helpers/open_conference_ware/proposals_helper_spec.rb +32 -0
  376. data/spec/helpers/open_conference_ware/rooms_helper_spec.rb +11 -0
  377. data/spec/helpers/open_conference_ware/session_types_helper_spec.rb +15 -0
  378. data/spec/helpers/open_conference_ware/time_range_helper_spec.rb +56 -0
  379. data/spec/helpers/open_conference_ware/tracks_helper_spec.rb +11 -0
  380. data/spec/helpers/open_conference_ware/user_favorites_helper_spec.rb +11 -0
  381. data/spec/integration/open_conference_ware/cache_lookups_mixin_spec.rb +122 -0
  382. data/spec/mixins/open_conference_ware/normalize_url_mixin_spec.rb +65 -0
  383. data/spec/models/open_conference_ware/authentication_spec.rb +67 -0
  384. data/spec/models/open_conference_ware/event_spec.rb +211 -0
  385. data/spec/models/open_conference_ware/proposal_spec.rb +606 -0
  386. data/spec/models/open_conference_ware/room_spec.rb +14 -0
  387. data/spec/models/open_conference_ware/schedule_item_spec.rb +12 -0
  388. data/spec/models/open_conference_ware/schedule_spec.rb +358 -0
  389. data/spec/models/open_conference_ware/selector_vote_spec.rb +12 -0
  390. data/spec/models/open_conference_ware/session_type_spec.rb +15 -0
  391. data/spec/models/open_conference_ware/snippet_spec.rb +13 -0
  392. data/spec/models/open_conference_ware/speaker_mailer_spec.rb +91 -0
  393. data/spec/models/open_conference_ware/track_spec.rb +36 -0
  394. data/spec/models/open_conference_ware/user_favorite_spec.rb +41 -0
  395. data/spec/models/open_conference_ware/user_spec.rb +77 -0
  396. data/spec/ocw_config.rb +76 -0
  397. data/spec/rcov.opts +2 -0
  398. data/spec/spec.opts +3 -0
  399. data/spec/spec_helper.rb +55 -0
  400. data/spec/spec_helper_customizations.rb +125 -0
  401. data/spec/support/add_all_helpers_to_view.rb +24 -0
  402. data/spec/support/authenticated_test_helper.rb +27 -0
  403. data/spec/support/fixture_shortcuts.rb +18 -0
  404. data/spec/support/omniauth.rb +34 -0
  405. data/spec/support/valid_params_extraction.rb +15 -0
  406. data/spec/views/open_conference_ware/proposals/_room_control.html.erb_spec.rb +23 -0
  407. data/spec/views/open_conference_ware/proposals/_transition_control.html.erb_spec.rb +14 -0
  408. data/spec/views/open_conference_ware/proposals/show.html.erb_spec.rb +128 -0
  409. data/spec/views/open_conference_ware/rooms/index.html.erb_spec.rb +19 -0
  410. data/spec/views/open_conference_ware/rooms/show.html.erb_spec.rb +15 -0
  411. data/spec/views/open_conference_ware/selector_votes/index.html.erb_spec.rb +41 -0
  412. data/spec/views/open_conference_ware/session_types/edit.html.erb_spec.rb +25 -0
  413. data/spec/views/open_conference_ware/session_types/index.html.erb_spec.rb +19 -0
  414. data/spec/views/open_conference_ware/session_types/new.html.erb_spec.rb +24 -0
  415. data/spec/views/open_conference_ware/session_types/show.html.erb_spec.rb +18 -0
  416. data/spec/views/open_conference_ware/tracks/edit.html.erb_spec.rb +25 -0
  417. data/spec/views/open_conference_ware/tracks/index.html.erb_spec.rb +56 -0
  418. data/spec/views/open_conference_ware/tracks/new.html.erb_spec.rb +26 -0
  419. data/spec/views/open_conference_ware/tracks/show.html.erb_spec.rb +23 -0
  420. data/spec/views/open_conference_ware/user_favorites/index.html.erb_spec.rb +29 -0
  421. data/spec/views/open_conference_ware/users/index_spec.rb +24 -0
  422. data/util/add_id3_tags_to_mp3.rb +57 -0
  423. data/util/schedule_demo.rb +36 -0
  424. data/util/seed_schedule.rb +61 -0
  425. data/util/sessions_to_lanyrd.rb +166 -0
  426. data/util/transfer_schedule_items.rb +25 -0
  427. data/util/update_proposal_status_from_voting_spreadsheet.rb +26 -0
  428. data/util/user_favorites_contention_report.rb +42 -0
  429. data/util/user_favorites_contention_report.sh +9 -0
  430. data/vendor/assets/fonts/glyphicons-halflings-regular.eot +0 -0
  431. data/vendor/assets/fonts/glyphicons-halflings-regular.svg +228 -0
  432. data/vendor/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
  433. data/vendor/assets/fonts/glyphicons-halflings-regular.woff +0 -0
  434. data/vendor/assets/images/farbtastic/marker.png +0 -0
  435. data/vendor/assets/images/farbtastic/mask.png +0 -0
  436. data/vendor/assets/images/farbtastic/wheel.png +0 -0
  437. data/vendor/assets/images/idselector/aol.ico +0 -0
  438. data/vendor/assets/images/idselector/arrow.gif +0 -0
  439. data/vendor/assets/images/idselector/arrow_white_back.png +0 -0
  440. data/vendor/assets/images/idselector/arrow_white_forward.png +0 -0
  441. data/vendor/assets/images/idselector/blogger.ico +0 -0
  442. data/vendor/assets/images/idselector/claimid.ico +0 -0
  443. data/vendor/assets/images/idselector/flickr.ico +0 -0
  444. data/vendor/assets/images/idselector/google.ico +0 -0
  445. data/vendor/assets/images/idselector/lj.ico +0 -0
  446. data/vendor/assets/images/idselector/myopenid.ico +0 -0
  447. data/vendor/assets/images/idselector/openid.ico +0 -0
  448. data/vendor/assets/images/idselector/technorati.ico +0 -0
  449. data/vendor/assets/images/idselector/verisign.ico +0 -0
  450. data/vendor/assets/images/idselector/vidoop2.ico +0 -0
  451. data/vendor/assets/images/idselector/vox.ico +0 -0
  452. data/vendor/assets/images/idselector/yahoo.ico +0 -0
  453. data/vendor/assets/javascripts/audiojs/audio.min.js +23 -0
  454. data/vendor/assets/javascripts/audiojs/audiojs.swf +0 -0
  455. data/vendor/assets/javascripts/audiojs/player-graphics.gif +0 -0
  456. data/vendor/assets/javascripts/bootstrap.js +1999 -0
  457. data/vendor/assets/javascripts/farbtastic.js +329 -0
  458. data/vendor/assets/javascripts/html5shiv.js +8 -0
  459. data/vendor/assets/javascripts/idselector.js +538 -0
  460. data/vendor/assets/javascripts/idselector~original.js +532 -0
  461. data/vendor/assets/javascripts/jquery-migrate-1.2.1.js +521 -0
  462. data/vendor/assets/javascripts/jquery.localScroll.js +106 -0
  463. data/vendor/assets/javascripts/jquery.scrollTo.js +174 -0
  464. data/vendor/assets/javascripts/respond.min.js +6 -0
  465. data/vendor/assets/stylesheets/bootstrap-theme.css +384 -0
  466. data/vendor/assets/stylesheets/bootstrap.css +6805 -0
  467. data/vendor/assets/stylesheets/farbtastic.css.scss +38 -0
  468. data/vendor/assets/stylesheets/persona-buttons.css +229 -0
  469. data/vendor/assets/stylesheets/reset-fonts-grids.css +7 -0
  470. metadata +1174 -0
  471. metadata.gz.sig +0 -0
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpenConferenceWare::AuthenticationsController do
4
+ routes { OpenConferenceWare::Engine.routes }
5
+
6
+ describe "GET sign_in" do
7
+ before { get :sign_in }
8
+
9
+ it "renders the sign_in template" do
10
+ expect(response).to render_template("sign_in")
11
+ end
12
+ end
13
+
14
+ describe "GET create" do
15
+ context "with no auth hash" do
16
+ before { get :create }
17
+
18
+ it "redirects to the sign in page" do
19
+ expect(response).to redirect_to(sign_in_path)
20
+ end
21
+ end
22
+
23
+ context "with an auth hash" do
24
+ context "matching an existing User's Authentication" do
25
+ let(:existing_user) { create(:user) }
26
+ let(:authentication) { create(:authentication,
27
+ provider: 'existing',
28
+ user: existing_user) }
29
+
30
+ before do
31
+ OmniAuth.config.add_mock(:existing, {uid: authentication.uid})
32
+ request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:existing]
33
+ end
34
+
35
+ shared_examples_for "signs the user in" do
36
+ it "signs the user in" do
37
+ expect(controller.current_user).to eq existing_user
38
+ end
39
+ end
40
+
41
+ context "when not already signed in" do
42
+ before { get :create }
43
+ include_examples "signs the user in"
44
+ end
45
+
46
+ context "when already signed in" do
47
+ before { login_as(create(:user)) }
48
+ before { get :create }
49
+ include_examples "signs the user in"
50
+ end
51
+ end
52
+
53
+ describe "containing an unknown UID" do
54
+ describe "when already signed in" do
55
+ let(:logged_in_user) { create(:user) }
56
+ let(:uid) { "logged-in:#{Time.now.to_i}" }
57
+
58
+ before do
59
+ login_as(logged_in_user)
60
+ OmniAuth.config.add_mock(:logged_in, {uid: uid})
61
+ request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:logged_in]
62
+
63
+ get :create
64
+ end
65
+
66
+ it "associates the new Authentication with the signed-in user" do
67
+ expect(logged_in_user.authentications.map(&:uid)).to include(uid)
68
+ end
69
+ end
70
+
71
+ describe "when not signed in" do
72
+ let(:uid) { "new-user:#{Time.now.to_i}" }
73
+ before do
74
+ OmniAuth.config.add_mock(:new_user, {uid: uid})
75
+ request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:new_user]
76
+
77
+ get :create
78
+ end
79
+
80
+ it "signs in as a new user" do
81
+ expect(controller.current_user.authentications.first.uid).to eq uid
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,146 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpenConferenceWare::CommentsController do
4
+ render_views
5
+ fixtures :all
6
+ routes { OpenConferenceWare::Engine.routes }
7
+
8
+ before do
9
+ @event = events(:open)
10
+ @proposal = proposals(:quentin_widgets)
11
+ end
12
+
13
+ describe "index" do
14
+ shared_examples_for "shared forbidden index behaviors" do
15
+ describe "HTML" do
16
+ before do
17
+ get :index
18
+ end
19
+
20
+ it "should get redirect" do
21
+ response.should be_redirect
22
+ end
23
+ end
24
+ end
25
+
26
+ shared_examples_for "shared allowed index behaviors" do
27
+ describe "Atom" do
28
+ it "should get error if not key was specified" do
29
+ get :index, format: "atom"
30
+
31
+ response.should_not be_success
32
+ response.should_not be_redirect
33
+ end
34
+
35
+ it "should get error if key is wrong" do
36
+ get :index, format: "atom", secret: "MEOW"
37
+
38
+ response.should_not be_success
39
+ response.should_not be_redirect
40
+ end
41
+
42
+ it "should get data if key is right" do
43
+ get :index, format: "atom", secret: OpenConferenceWare::CommentsController::SECRET
44
+
45
+ response.should be_success
46
+ comments = assigns(:comments)
47
+ struct = Hash.from_xml(response.body)
48
+ struct['feed']['entry'].size.should == comments.size
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "anonymous user" do
54
+ it_should_behave_like "shared forbidden index behaviors"
55
+ it_should_behave_like "shared allowed index behaviors"
56
+ end
57
+
58
+ describe "mortal user" do
59
+ before do
60
+ login_as :quentin
61
+ end
62
+
63
+ it_should_behave_like "shared forbidden index behaviors"
64
+ it_should_behave_like "shared allowed index behaviors"
65
+ end
66
+
67
+ describe "admin user" do
68
+ before do
69
+ login_as :aaron
70
+ end
71
+
72
+ after do
73
+ logout
74
+ end
75
+
76
+ it_should_behave_like "shared allowed index behaviors"
77
+
78
+ describe "HTML" do
79
+ before do
80
+ get :index
81
+ end
82
+
83
+ it "should display comments" do
84
+ response.should be_success
85
+ assigns(:comments).size.should > 0
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "create" do
92
+ it "should reject comments from bots" do
93
+ post :create, proposal_id: @proposal.to_param, quagmire: "omg"
94
+
95
+ flash[:failure].should match(/robot/i)
96
+ response.should be_redirect
97
+ end
98
+
99
+ it "should fail on empty comment" do
100
+ email = "bubba@smith.com"
101
+ message = "Yo"
102
+ post :create, proposal_id: @proposal.to_param, comment: {email: "bubba@smith.com"}
103
+
104
+ flash.keys.should include(:failure)
105
+ assigns(:comment).should_not be_valid
106
+ end
107
+
108
+ it "should fail on incomplete comment" do
109
+ post :create, proposal_id: @proposal.to_param, comment: {email: "bubba@smith.com", message: ""}
110
+
111
+ flash.keys.should include(:failure)
112
+ assigns(:comment).should_not be_valid
113
+ end
114
+
115
+ it "should create new comment" do
116
+ email = "bubba@smith.com"
117
+ message = "Yo"
118
+ post :create, proposal_id: @proposal.to_param, comment: {email: email, message: message}
119
+
120
+ assigns(:comment).should be_valid
121
+ flash.keys.should_not include(:failure)
122
+ response.should redirect_to(proposal_url(@proposal, commented: true))
123
+ end
124
+
125
+ it "should assign email if logged in" do
126
+ login_as :quentin
127
+ post :create, proposal_id: @proposal.to_param, comment: {message: "Yo"}
128
+
129
+ comment = assigns(:comment)
130
+ comment.email.should == users(:quentin).email
131
+ end
132
+ end
133
+
134
+ describe "destroy" do
135
+ it "should destroy" do
136
+ login_as :aaron
137
+ comment = comments(:clio_chupacabras_fbi)
138
+ Comment.should_receive(:find).with(comment.to_param).and_return(comment)
139
+ comment.should_receive(:destroy)
140
+ delete :destroy, id: comment.to_param, format: :html
141
+
142
+ flash.keys.should include(:success)
143
+ # response.should be_redirect # TODO why not?
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpenConferenceWare::EventsController, "when displaying events" do
4
+ render_views
5
+ fixtures :all
6
+ routes { OpenConferenceWare::Engine.routes }
7
+
8
+ describe "index" do
9
+ it "should display error if there's no current event" do
10
+ Event.should_receive(:current).at_least(:once).and_return(nil)
11
+ get :index
12
+
13
+ response.should be_success
14
+ flash.keys.should include(:failure)
15
+ end
16
+
17
+ it "should display a list of events" do
18
+ get :index
19
+
20
+ response.should be_success
21
+ assigns(:events).should_not be_blank
22
+ end
23
+ end
24
+
25
+ describe "show" do
26
+ describe "non-existent event" do
27
+ before do
28
+ get :show, id: -1
29
+ end
30
+
31
+ it "should display error" do
32
+ flash.keys.should include(:failure)
33
+ end
34
+
35
+ it "should redirect to current event" do
36
+ response.should redirect_to(event_path(events(:open)))
37
+ end
38
+ end
39
+
40
+ describe "extant event" do
41
+ before do
42
+ @event = events(:closed)
43
+ get :show, id: @event.slug
44
+ end
45
+
46
+ it "should display event" do
47
+ response.should redirect_to(event_proposals_path(@event))
48
+ end
49
+ end
50
+ end
51
+
52
+ describe "speakers" do
53
+ before do
54
+ @event = events(:open)
55
+ stub_current_event!(event: @event)
56
+ end
57
+
58
+ describe "before proposals statuses published" do
59
+ before do
60
+ @event.stub(:proposal_status_published? => false)
61
+
62
+ get :speakers, id: @event.to_param
63
+ end
64
+
65
+ it "should redirect to event's proposals" do
66
+ response.should redirect_to(event_proposals_path(@event))
67
+ end
68
+
69
+ it "should display a flash error" do
70
+ flash[:failure].should_not be_blank
71
+ end
72
+
73
+ end
74
+
75
+ describe "after proposals statuses published" do
76
+ before do
77
+ @event.stub(:proposal_status_published? => true)
78
+ @event.stub(:schedule_published? => true)
79
+
80
+ get :speakers, id: @event.to_param
81
+ end
82
+
83
+ it "should get a speaker's page" do
84
+ response.should be_success
85
+ end
86
+
87
+ it "should see speakers" do
88
+ response.body.should have_selector(".fn", text: /#{users(:quentin).fullname}/)
89
+ end
90
+
91
+ it "should see sessions" do
92
+ response.body.should have_selector(".summary", text: proposals(:postgresql_session).title)
93
+ end
94
+
95
+ it "should not see non-confirmed proposals" do
96
+ response.body.should_not have_selector(".summary", text: proposals(:clio_chupacabras).title)
97
+ end
98
+ end
99
+
100
+ end
101
+ end
102
+
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpenConferenceWare::Manage::EventsController do
4
+ render_views
5
+ fixtures :all
6
+ routes { OpenConferenceWare::Engine.routes }
7
+
8
+ before(:each) do
9
+ @event = events(:open)
10
+ login_as users(:aaron)
11
+ end
12
+
13
+ it "should retreive event show page" do
14
+ get :index, id: @event.slug
15
+
16
+ response.should be_success
17
+ end
18
+
19
+ it "should retrieve new event form" do
20
+ get :new, id: @event.slug
21
+
22
+ response.should be_success
23
+ end
24
+
25
+ it "should retrieve edit event form" do
26
+ get :edit, id: @event.slug
27
+
28
+ response.should be_success
29
+ end
30
+
31
+ it "should update event" do
32
+ stub_current_event!(event: @event)
33
+ attributes = { "title" => "omgwtfbbq" }
34
+ @event.should_receive(:assign_attributes).with(attributes)
35
+
36
+ put :update, id: @event.slug, event: attributes
37
+
38
+ response.should be_redirect
39
+ flash[:notice].should_not be_blank
40
+ end
41
+
42
+ it "should create event"
43
+
44
+ it "should destroy event"
45
+
46
+ def setup_proposals(&block)
47
+ @proposal_ids = ""
48
+ @proposals = []
49
+ %w[aaron_aardvarks quentin_widgets].each do |slug|
50
+ proposal = proposals(slug)
51
+ block.call proposal
52
+ @proposal_ids << "#{proposal.id},"
53
+ @proposals << proposal
54
+ end
55
+ end
56
+
57
+ def assert_notified
58
+ response.should be_redirect
59
+ flash[:success].should =~ /aaron@example.com/
60
+ flash[:success].should =~ /quentin@example.com/
61
+ end
62
+
63
+ it "should raise an error if trying to notify speakers other than accepted or rejected" do
64
+ setup_proposals { |proposal| proposal.accept! }
65
+ lambda { post :notify_speakers, { id: @event.slug, proposal_ids: @proposal_ids } }.should raise_error(ArgumentError)
66
+ end
67
+
68
+ it "should skip proposals that don't exist" do
69
+ setup_proposals { |proposal| proposal.accept! }
70
+ post :notify_speakers, { id: @event.slug, proposal_ids: @proposal_ids+',999', proposal_status: 'accepted' }
71
+
72
+ assert_notified
73
+ end
74
+
75
+ it "should skip proposals that have already been notified" do
76
+ setup_proposals do |proposal|
77
+ proposal.accept!
78
+ proposal.notified_at = Time.now
79
+ proposal.save
80
+ end
81
+ post :notify_speakers, { id: @event.slug, proposal_ids: @proposal_ids, proposal_status: 'accepted' }
82
+
83
+ assert_notified
84
+ flash[:success].should =~ /none/
85
+ flash[:success].should =~ /already been notified/
86
+ end
87
+
88
+ it "should notify accepted speakers" do
89
+ setup_proposals { |proposal| proposal.accept! }
90
+ post :notify_speakers, { id: @event.slug, proposal_ids: @proposal_ids, proposal_status: 'accepted' }
91
+
92
+ assert_notified
93
+ flash[:success].should_not =~ /already been notified/
94
+ end
95
+
96
+ it "should notify rejected speakers" do
97
+ setup_proposals { |proposal| proposal.reject! }
98
+ post :notify_speakers, { id: @event.slug, proposal_ids: @proposal_ids, proposal_status: 'rejected' }
99
+
100
+ assert_notified
101
+ flash[:success].should_not =~ /already been notified/
102
+ end
103
+ end
@@ -0,0 +1,1273 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpenConferenceWare::ProposalsController do
4
+ render_views
5
+ fixtures :all
6
+ routes { OpenConferenceWare::Engine.routes }
7
+
8
+ # Return an array of Proposal objects extracted from the response body.
9
+ def extract_proposals
10
+ return assert_select('.proposal_row').map{|n| Proposal.find(n.attributes['id'].gsub(/^proposal_row_/, ''))}
11
+ end
12
+
13
+ before do
14
+ @event = events(:open)
15
+ end
16
+
17
+ describe "index" do
18
+ describe "when returning HTML" do
19
+ before do
20
+ get :index, event_id: @event.slug
21
+ end
22
+
23
+ it "should be successful" do
24
+ response.should be_success
25
+ end
26
+
27
+ it "should assign an event" do
28
+ assigns(:event).should == @event
29
+ end
30
+
31
+ it "should assign proposals" do
32
+ assigns(:proposals).should_not be_blank
33
+ end
34
+ end
35
+
36
+ describe "when returning CSV" do
37
+
38
+ def get_csv_index
39
+ OpenConferenceWare.stub(have_user_profiles: true)
40
+ OpenConferenceWare.stub(have_multiple_presenters: true)
41
+ stub_current_event!(event: @event)
42
+
43
+ get :index, event_id: @event.to_param, format: "csv"
44
+
45
+ @rows = CSV.parse(response.body)
46
+ @header = @rows.shift
47
+ end
48
+
49
+ shared_examples_for "shared CSV behaviors" do
50
+
51
+ it "should return CSV" do
52
+ @rows.should be_a_kind_of(Array)
53
+ end
54
+
55
+ it "should see public fields" do
56
+ @header.should include("Title")
57
+ end
58
+ end
59
+
60
+ shared_examples_for "shared non-admin CSV behaviors" do
61
+ it "should not see private fields" do
62
+ @header.should_not include("Emails")
63
+ end
64
+ end
65
+
66
+ describe "anonymous user" do
67
+ before do
68
+ logout
69
+ end
70
+
71
+ describe "with visible schedule" do
72
+ before do
73
+ @controller.stub(:schedule_visible? => true)
74
+ get_csv_index
75
+ end
76
+
77
+ it_should_behave_like "shared CSV behaviors"
78
+
79
+ it_should_behave_like "shared non-admin CSV behaviors"
80
+
81
+ it "should see schedule fields" do
82
+ @header.should include("Start Time")
83
+ end
84
+ end
85
+
86
+ describe "without visible schedule" do
87
+ before do
88
+ @controller.stub(:schedule_visible? => false)
89
+ get_csv_index
90
+ end
91
+
92
+ it_should_behave_like "shared CSV behaviors"
93
+
94
+ it_should_behave_like "shared non-admin CSV behaviors"
95
+
96
+ it "should not see schedule fields" do
97
+ @header.should_not include("Start Time")
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ describe "mortal user" do
104
+ before do
105
+ login_as(:quentin)
106
+ get_csv_index
107
+ end
108
+
109
+ it_should_behave_like "shared CSV behaviors"
110
+
111
+ it_should_behave_like "shared non-admin CSV behaviors"
112
+ end
113
+
114
+ describe "admin user" do
115
+ before do
116
+ controller.stub(:admin?).and_return(true)
117
+ get_csv_index
118
+ end
119
+
120
+ it_should_behave_like "shared CSV behaviors"
121
+
122
+ it "should see private fields" do
123
+ @header.should include("Emails")
124
+ end
125
+ end
126
+ end
127
+
128
+ shared_examples_for "when exporting" do
129
+ # Expects following to be set by implementor's #before block:
130
+ # - @proposals
131
+ # - @records
132
+ # - @record
133
+
134
+ it "should assign multiple items" do
135
+ @proposals.size.should >= 1
136
+ end
137
+
138
+ it "should export same number of items as assigned" do
139
+ @records.size.should == @proposals.size
140
+ end
141
+
142
+ it "should export presenter" do
143
+ @record.keys.should include('presenter')
144
+ end
145
+
146
+ it "should not export email" do
147
+ @record.keys.should_not include('email')
148
+ end
149
+
150
+ it "should not export private notes" do
151
+ @record.keys.should_not include('note_to_organizers')
152
+ end
153
+ end
154
+
155
+ describe "when returning XML" do
156
+ before(:each) do
157
+ get :index, event_id: @event.slug, format: "xml"
158
+
159
+ @proposals = assigns(:proposals)
160
+ @struct = Hash.from_xml(response.body)
161
+ @records = @struct['open_conference_ware_proposals']
162
+ @record = @records.first
163
+ end
164
+
165
+ it_should_behave_like "when exporting"
166
+ end
167
+
168
+ describe "when returning JSON" do
169
+ before(:each) do
170
+ get :index, event_id: @event.slug, format: "json"
171
+
172
+ @proposals = assigns(:proposals)
173
+ @struct = ActiveSupport::JSON.decode(response.body)
174
+ @records = @struct
175
+ @record = @records.first["proposal"]
176
+ end
177
+
178
+ it_should_behave_like "when exporting"
179
+ end
180
+
181
+ describe "when sorting" do
182
+ it "should sort proposals by title" do
183
+ get :index, sort: "title", event_id: @event.to_param
184
+ extracted = extract_proposals
185
+
186
+ extracted.size.should > 0
187
+ values = extracted.map(&:title)
188
+ expected = values.sort_by(&:downcase)
189
+ values.should == expected
190
+ end
191
+
192
+ it "should sort proposals by track" do
193
+ get :index, sort: "track", event_id: @event.to_param
194
+ proposals = extract_proposals
195
+
196
+ proposals.size.should > 0
197
+
198
+ tracks_returned = proposals.map{|proposal| proposal.track.title}
199
+ tracks_expected = tracks_returned.sort_by(&:downcase)
200
+ tracks_returned.should == tracks_expected
201
+ end
202
+
203
+ it "should sort proposals by title descending" do
204
+ get :index, sort: "title", dir: "desc", event_id: @event.to_param
205
+ proposals = extract_proposals
206
+
207
+ proposals.size.should > 0
208
+
209
+ titles_returned = proposals.map(&:title)
210
+ titles_expected = titles_returned.sort_by(&:downcase).reverse
211
+ titles_returned.should == titles_expected
212
+ end
213
+
214
+ it "should sort proposals by start time" do
215
+ get :sessions_index, sort: "start_time", event_id: @event.to_param
216
+ proposals = extract_proposals
217
+
218
+ proposals.size.should > 0
219
+
220
+ values = proposals.map(&:start_time)
221
+ expected = values.sort
222
+ values.should == expected
223
+ end
224
+
225
+ it "should not sort proposals by forbidden field" do
226
+ Proposal.any_instance.should_not_receive(:destroy)
227
+ get :index, sort: "destroy", event_id: @event.to_param
228
+
229
+ # default to sorting by submitted_at
230
+ proposals = extract_proposals
231
+ proposals.size.should > 0
232
+ values = proposals.map(&:submitted_at)
233
+ expected = values.sort
234
+ values.should == expected
235
+ end
236
+
237
+ end
238
+
239
+ describe "when returning ATOM" do
240
+ def get_entry(proposal_symbol)
241
+ title = proposals(proposal_symbol).title
242
+ return @doc.xpath("//entry/title[text()='#{title}']")
243
+ end
244
+
245
+ describe "for /proposals.atom" do
246
+ before do
247
+ get :index, format: "atom"
248
+ @doc = Nokogiri.parse(response.body)
249
+ end
250
+
251
+ it "should include proposals from multiple events" do
252
+ get_entry(:clio_chupacabras).should_not be_empty
253
+ get_entry(:aaron_aardvarks).should_not be_empty
254
+ end
255
+ end
256
+
257
+ describe "for /events/:event_id/proposals.atom" do
258
+ before do
259
+ get :index, format: "atom", event_id: @event.slug
260
+ @doc = Nokogiri.parse(response.body)
261
+ end
262
+
263
+ it "should include proposals from this event" do
264
+ get_entry(:aaron_aardvarks).should_not be_empty
265
+ end
266
+
267
+ it "should not include proposals from other events" do
268
+ get_entry(:clio_chupacabras).should be_empty
269
+ end
270
+ end
271
+ end
272
+
273
+ end
274
+
275
+ describe "sessions" do
276
+ it "should display session_text" do
277
+ event = stub_model(Event,
278
+ :proposal_status_published? => true,
279
+ id: 1234,
280
+ slug: 'event_slug',
281
+ session_text: "MySessionText",
282
+ populated_sessions: []
283
+ )
284
+ stub_current_event!(event: event)
285
+
286
+ get :sessions_index, event_id: event.to_param
287
+ response.body.should have_selector(".event_text.session_text") do |text|
288
+ text.should contain("MySessionText")
289
+ end
290
+ end
291
+
292
+ it "should display a list of sessions" do
293
+ proposal = stub_model(Proposal, state: "confirmed", users: [])
294
+ proposals = [proposal]
295
+ event = stub_model(Event,
296
+ :proposal_status_published? => true,
297
+ id: 1234,
298
+ slug: 'event_slug',
299
+ populated_sessions: proposals
300
+ )
301
+
302
+ stub_current_event!(event: event)
303
+
304
+ # Bypass #fetch_object because it can't cache our singleton doubles.
305
+ Proposal.stub(:fetch_object).and_return do |slug, callback|
306
+ callback.call
307
+ end
308
+
309
+ get :sessions_index, event_id: event.to_param
310
+ expect(assigns(:proposals)).to be_a_kind_of(proposals.class)
311
+ end
312
+
313
+ it "should redirect to proposals unless the proposal status is published" do
314
+ event = stub_model(Event, :proposal_status_published? => false, id: 1234, slug: 'event_slug')
315
+ stub_current_event!(event: event)
316
+ get :sessions_index, event_id: event.to_param
317
+
318
+ response.should redirect_to(event_proposals_url(event))
319
+ end
320
+
321
+ it "should redirect /sessions to proposals unless proposal status is published" do
322
+ event = stub_model(Event, :proposal_status_published? => false, id: 1234, slug: 'event_slug')
323
+ stub_current_event!(event: event, status: :assigned_to_current)
324
+ get :sessions_index, format: :html
325
+
326
+ response.should redirect_to(event_proposals_url(event))
327
+ end
328
+
329
+ it "should normalize /sessions if proposal status is published" do
330
+ event = stub_model(Event, :proposal_status_published? => true, id: 1234, slug: 'event_slug')
331
+ stub_current_event!(event: event, status: :assigned_to_current)
332
+ get :sessions_index, format: :html
333
+
334
+ response.should redirect_to(event_sessions_path(event))
335
+ end
336
+
337
+ it "should normalize /schedule if proposal status is published" do
338
+ event = stub_model(Event, :proposal_status_published? => true, :schedule_published? => true, id: 1234, slug: 'event_slug')
339
+ stub_current_event!(event: event, status: :assigned_to_current)
340
+ get :schedule, format: :html
341
+
342
+ response.should redirect_to(event_schedule_path(event))
343
+ end
344
+ end
345
+
346
+ describe "show" do
347
+ it "should display extant proposal" do
348
+ proposal = proposals(:quentin_widgets)
349
+ get :show, id: proposal.id
350
+
351
+ response.should be_success
352
+ assigns(:proposal).should == proposal
353
+ end
354
+
355
+ it "should redirect back to proposals list if asked to display a non-existent proposal" do
356
+ get :show, id: -1
357
+
358
+ flash[:failure].should_not be_blank
359
+ response.should redirect_to(proposals_url)
360
+ end
361
+
362
+ it "should redirect back to proposals list if asked to display a proposal without an event" do
363
+ proposal = proposals(:quentin_widgets)
364
+ proposal.stub(event: nil)
365
+ Proposal.stub(find_by_id: proposal, find: proposal, lookup: proposal)
366
+
367
+ get :show, id: proposal.id
368
+
369
+ flash[:failure].should_not be_blank
370
+ response.should redirect_to(event_proposals_path(events(:open)))
371
+ end
372
+
373
+ describe "redirect" do
374
+ # Options:
375
+ # * published: Are proposal statuses published for this event?
376
+ # * confirmed: Is this proposal confirmed?
377
+ # * session: Is this proposal being accessed via a sessions#show route?
378
+ # * redirect: Redirect to where? (:proposal, :session, nil)
379
+ def assert_show(opts={}, &block)
380
+ @key = 123
381
+ @event.stub(:proposal_status_published?).and_return(opts[:published])
382
+ stub_current_event!(event: @event)
383
+
384
+ @users = []
385
+ @users.stub(:by_name).and_return([])
386
+
387
+ @proposal = stub_model(Proposal, id: @key, event: @event, track: @event.tracks.empty? ? nil : Track.first, users: @users)
388
+ @proposal.stub(:confirmed?).and_return(opts[:confirmed])
389
+ controller.stub(:get_proposal_and_assignment_status).and_return([@proposal, :assigned_via_param])
390
+ get opts[:session] ? :session_show : :show, id: @key
391
+ case opts[:redirect]
392
+ when :proposal
393
+ response.should redirect_to(proposal_path(@key))
394
+ when :session
395
+ response.should redirect_to(session_path(@key))
396
+ when nil, false
397
+ response.should be_success
398
+ else
399
+ end
400
+ end
401
+
402
+ describe "when status published" do
403
+ it "should redirect confirmed proposal to session" do
404
+ assert_show published: true, confirmed: true, session: false, redirect: :session
405
+ end
406
+
407
+ it "should redirect non-session to proposal" do
408
+ assert_show published: true, confirmed: false, session: true, redirect: :proposal
409
+ end
410
+
411
+ it "should display session" do
412
+ assert_show published: true, confirmed: true, session: true, redirect: false
413
+ end
414
+
415
+ it "should display proposal" do
416
+ assert_show published: true, confirmed: false, session: false, redirect: false
417
+ end
418
+ end
419
+
420
+ describe "when status not published" do
421
+ it "should allow admin to view sessions" do
422
+ login_as :aaron
423
+ assert_show published: false, confirmed: true, session: true, redirect: false
424
+ end
425
+
426
+ it "should redirect confirmed proposal to proposals" do
427
+ assert_show published: false, confirmed: true, session: true, redirect: :proposal
428
+ end
429
+
430
+ it "should redirect non-session to proposal" do
431
+ assert_show published: false, confirmed: false, session: true, redirect: :proposal
432
+ end
433
+
434
+ it "should display confirmed proposal" do
435
+ assert_show published: false, confirmed: true, session: false, redirect: false
436
+ end
437
+
438
+ it "should display session as proposal" do
439
+ assert_show published: false, confirmed: false, session: false, redirect: false
440
+ end
441
+ end
442
+
443
+ describe "non-current event" do
444
+ render_views false
445
+ it "should not redirect a published session of an old event if current event isn't publishing sesions" do
446
+ current_event = stub_model(Event, slug: 'new', proposal_status_published: false)
447
+ old_event = stub_model(Event, slug: 'old', proposal_status_published: true)
448
+ old_session_user = users(:clio)
449
+ old_session = stub_model(Proposal, status: 'confirmed', event: old_event)
450
+ old_session.users << old_session_user
451
+
452
+ Proposal.stub(find: old_session, find_by_id: old_session, lookup: old_session)
453
+ Event.stub(current: current_event)
454
+
455
+ get :session_show, id: old_session.id
456
+ response.should be_success
457
+ end
458
+ end
459
+ end
460
+
461
+ describe "accepted proposal" do
462
+ before do
463
+ @proposal = proposals(:quentin_widgets)
464
+ @proposal.accept!
465
+ end
466
+
467
+ it "should notify owners of acceptance" do
468
+ login_as(users(:quentin))
469
+ get :show, id: @proposal.id
470
+ response.body.should have_selector("h3", text: 'Congratulations')
471
+ end
472
+
473
+ it "should not notify non-owners of acceptance" do
474
+ get :show, id: @proposal.id
475
+ response.body.should_not have_selector("h3", text: 'Congratulations')
476
+ end
477
+
478
+ it "should not notify owners of acceptance if proposal confirmation controls are not visible" do
479
+ event = @proposal.event
480
+ event.stub(:show_proposal_confirmation_controls? => false)
481
+ Proposal.stub(lookup: @proposal)
482
+
483
+ login_as(users(:quentin))
484
+
485
+ get :show, id: @proposal.id
486
+ response.body.should_not have_selector("h3", text: 'Congratulations')
487
+ end
488
+ end
489
+
490
+ describe "not-accepted proposal" do
491
+ before do
492
+ login_as(users(:quentin))
493
+ @proposal = proposals(:quentin_widgets)
494
+ end
495
+
496
+ it "should not notify proposed proposal owners of acceptance" do
497
+ get :show, id: @proposal.id
498
+ response.body.should_not have_selector("h3", text: 'Congratulations')
499
+ end
500
+
501
+ it "should not notify rejected proposal owners of acceptance" do
502
+ @proposal.reject!
503
+ get :show, id: @proposal.id
504
+ response.body.should_not have_selector("h3", text: 'Congratulations')
505
+ end
506
+
507
+ it "should not notify junk proposal owners of acceptance" do
508
+ @proposal.mark_as_junk!
509
+ get :show, id: @proposal.id
510
+ response.body.should_not have_selector("h3", text: 'Congratulations')
511
+ end
512
+ end
513
+
514
+ end
515
+
516
+ describe "new" do
517
+ describe "for open event" do
518
+ describe "with user_profiles?" do
519
+ before(:each) do
520
+ OpenConferenceWare.stub(have_user_profiles: true)
521
+ end
522
+
523
+ it "should redirect incomplete profiles to user edit form" do
524
+ user = users(:incognito)
525
+ login_as(user)
526
+ get :new, event_id: events(:open).slug
527
+
528
+ flash.keys.should include(:notice)
529
+ response.should redirect_to(edit_user_path(user, require_complete_profile: true))
530
+ end
531
+
532
+ it "should allow users with complete profiles" do
533
+ login_as(:quentin)
534
+ get :new, event_id: events(:open).slug
535
+
536
+ flash.keys.should_not include(:failure)
537
+ response.should be_success
538
+ end
539
+ end
540
+
541
+ describe "without user_profiles?" do
542
+ before(:each) do
543
+ OpenConferenceWare.stub(have_user_profiles: false)
544
+ end
545
+
546
+ describe "with anonymous_proposals" do
547
+ before(:each) do
548
+ OpenConferenceWare.stub(have_anonymous_proposals: true)
549
+ end
550
+
551
+ it "should display form for open events" do
552
+ get :new, event_id: events(:open).slug
553
+
554
+ response.should be_success
555
+ assigns(:proposal).should be_true
556
+ end
557
+
558
+ it "should not assign presenter if anonymous" do
559
+ logout
560
+ get :new, event_id: events(:open).slug
561
+
562
+ response.should be_success
563
+ proposal = assigns(:proposal)
564
+ proposal.presenter.should be_blank
565
+ end
566
+ end
567
+
568
+ describe "without anonymous_proposals" do
569
+ before(:each) do
570
+ OpenConferenceWare.stub(have_anonymous_proposals: false)
571
+ end
572
+
573
+ it "should redirect anonymous user to login" do
574
+ get :new, event_id: events(:open).slug
575
+
576
+ flash.keys.should include(:notice)
577
+ response.should redirect_to(sign_in_path)
578
+ end
579
+ end
580
+
581
+ it "should assign presenter if logged in" do
582
+ user = users(:quentin)
583
+ login_as(user)
584
+ get :new, event_id: events(:open).slug
585
+
586
+ response.should be_success
587
+ proposal = assigns(:proposal)
588
+ proposal.presenter.should == user.fullname
589
+ end
590
+
591
+ describe "when an event can have tracks" do
592
+ it "should assign a track if there's only one" do
593
+ event = create(:event)
594
+ event.session_types << build(:session_type)
595
+ track = create(:track, event: event)
596
+ user = create(:user)
597
+ login_as(user)
598
+
599
+ get :new, event_id: event.slug
600
+
601
+ flash[:failure].should be_nil
602
+ assigns(:proposal).track.should == track
603
+ end
604
+
605
+ it "should not assign a track if there's more than one" do
606
+ event = create(:event)
607
+ event.session_types << build(:session_type)
608
+ track1 = create(:track, event: event)
609
+ track2 = create(:track, event: event)
610
+ user = create(:user)
611
+ login_as(user)
612
+
613
+ get :new, event_id: event.slug
614
+
615
+ flash[:failure].should be_nil
616
+ assigns(:proposal).track.should be_nil
617
+ end
618
+ end
619
+
620
+ describe "when event can have session types" do
621
+ it "should assign a session type if there's only one" do
622
+ event = create(:event)
623
+ event.tracks << build(:track)
624
+ session_type = create(:session_type, event: event)
625
+ user = create(:user)
626
+ login_as(user)
627
+
628
+ get :new, event_id: event.slug
629
+
630
+ assigns(:proposal).session_type.should == session_type
631
+ end
632
+
633
+ it "should not assign a session type if there's more than one" do
634
+ event = create(:event)
635
+ event.tracks << build(:track)
636
+ session_type1 = create(:session_type, event: event)
637
+ session_type2 = create(:session_type, event: event)
638
+ user = create(:user)
639
+ login_as(user)
640
+
641
+ get :new, event_id: event.slug
642
+
643
+ assigns(:proposal).session_type.should be_nil
644
+ end
645
+ end
646
+ end
647
+ end
648
+
649
+ describe "with closed event" do
650
+ it "should not display form" do
651
+ login_as(users(:quentin))
652
+ event = events(:closed)
653
+ get :new, event_id: event.to_param
654
+
655
+ response.should redirect_to(event_proposals_path(event))
656
+ end
657
+ end
658
+ end
659
+
660
+ describe "edit" do
661
+ before do
662
+ @proposal = proposals(:quentin_widgets)
663
+ end
664
+
665
+ shared_examples_for "shared allowed edit behaviors" do
666
+ it "should not redirect with failure" do
667
+ get :edit, id: @proposal.id, event_id: @event.to_param
668
+ flash.keys.should_not include(:failure)
669
+ response.should be_success
670
+ end
671
+ end
672
+
673
+ shared_examples_for "shared forbidden edit behaviors" do
674
+ it "should redirect with failure" do
675
+ get :edit, id: @proposal.id, event_id: @event.to_param
676
+ flash.keys.should include(:failure)
677
+ response.should redirect_to(proposal_path(@proposal))
678
+ end
679
+ end
680
+
681
+ describe "anonymous user" do
682
+ before(){ logout }
683
+
684
+ it "should redirect to login" do
685
+ get :edit, id: @proposal.id, event_id: @event.to_param
686
+ response.should redirect_to(sign_in_path)
687
+ end
688
+ end
689
+
690
+ describe "non-owner mortal user" do
691
+ before(){ login_as :clio }
692
+ it_should_behave_like "shared forbidden edit behaviors"
693
+ end
694
+
695
+ describe "owner mortal user" do
696
+ before(){ login_as :quentin }
697
+ it_should_behave_like "shared allowed edit behaviors"
698
+ end
699
+
700
+ describe "admin user" do
701
+ before { login_as :aaron }
702
+ it_should_behave_like "shared allowed edit behaviors"
703
+ end
704
+
705
+ describe "when closed" do
706
+ it "should redirect if owner tries to edit proposal for closed event" do
707
+ proposal = proposals(:clio_chupacabras)
708
+ login_as :clio
709
+ get :edit, id: proposal.id
710
+
711
+ pending "FIXME when should people not be able to edit proposals?"
712
+ response.should redirect_to(event_proposals_path(proposal.event))
713
+ end
714
+
715
+ it "should allow admin to edit" do
716
+ proposal = proposals(:clio_chupacabras)
717
+ login_as :aaron
718
+ get :edit, id: proposal.id
719
+
720
+ response.should be_success
721
+ assigns(:proposal).should == proposal
722
+ end
723
+ end
724
+ end
725
+
726
+ describe "create" do
727
+ # Try to create a proposal.
728
+ #
729
+ # Arguments:
730
+ # * login: User to login as, can be nil for none, symbol or user object.
731
+ # * inputs: Hash of properties to create a proposal from.
732
+ def assert_create(login=nil, inputs={}, &block)
733
+ login ? login_as(login) : logout
734
+ # TODO extract :commit into separate argument
735
+ post :create, inputs.reverse_merge(commit: 'really')
736
+ @record = assigns(:proposal)
737
+ block.call
738
+ end
739
+
740
+ before do
741
+ # TODO test other settings combinations
742
+ OpenConferenceWare.stub(have_proposal_excerpts: false)
743
+ OpenConferenceWare.stub(have_multiple_presenters: false)
744
+ OpenConferenceWare.stub(have_user_profiles: false)
745
+
746
+ @inputs = proposals(:quentin_widgets).attributes
747
+ @record = nil
748
+ end
749
+
750
+ describe "with user_profiles?" do
751
+ before(:each) do
752
+ OpenConferenceWare.stub(have_user_profiles: true)
753
+ end
754
+
755
+ it "should fail to create proposal without a complete user" do
756
+ user = users(:quentin)
757
+ user.should_receive(:complete_profile?).at_least(:once).and_return(false)
758
+ User.should_receive(:find).and_return(user)
759
+ proposal = Proposal.new(@inputs)
760
+ proposal.users << user
761
+ Proposal.should_receive(:new).and_return(proposal)
762
+ assert_create(user, event_id: @event.slug, proposal: @inputs) do
763
+ response.should be_success
764
+ proposal = assigns(:proposal)
765
+ proposal.should_not be_valid
766
+ end
767
+ end
768
+ end
769
+
770
+ describe "without user_profiles?" do
771
+ before(:each) do
772
+ OpenConferenceWare.stub(have_user_profiles: false)
773
+ end
774
+
775
+ describe "with anonymous proposals" do
776
+ before(:each) do
777
+ OpenConferenceWare.stub(have_anonymous_proposals: true)
778
+ end
779
+
780
+ it "should create proposal for anonymous user" do
781
+ assert_create(nil, event_id: @event.slug, proposal: @inputs) do
782
+ proposal = assigns(:proposal)
783
+ proposal.should be_valid
784
+ proposal.id.should_not be_nil
785
+ end
786
+ end
787
+
788
+ it "should preview proposal for anonymous user" do
789
+ @inputs['title'] = ''
790
+ assert_create(nil, event_id: @event.slug, proposal: @inputs, submit: nil, preview: 'Preview') do
791
+ proposal = assigns(:proposal)
792
+ proposal.errors.should_not be_empty
793
+ proposal.should_not be_valid
794
+ proposal.id.should be_nil
795
+ end
796
+ end
797
+ end
798
+
799
+ describe "without anonymous proposals" do
800
+ before(:each) do
801
+ OpenConferenceWare.stub(have_anonymous_proposals: false)
802
+ end
803
+
804
+ it "should not create proposal for anonymous user" do
805
+ assert_create(nil, event_id: @event.slug, proposal: @inputs) do
806
+ response.should redirect_to(sign_in_path)
807
+ end
808
+ end
809
+ end
810
+
811
+ it "should create proposal for mortal user" do
812
+ assert_create(:quentin, event_id: @event.slug, proposal: @inputs) do
813
+ proposal = assigns(:proposal)
814
+ proposal.should be_valid
815
+ proposal.id.should_not be_nil
816
+ end
817
+ end
818
+
819
+ it "should fail to create proposal without a presenter" do
820
+ inputs = @inputs.clone
821
+ inputs['presenter'] = nil
822
+ assert_create(:quentin, event_id: @event.slug, proposal: inputs) do
823
+ response.should be_success
824
+ proposal = assigns(:proposal)
825
+ proposal.should_not be_valid
826
+ end
827
+ end
828
+ end
829
+
830
+ describe "success page" do
831
+ before(:each) do
832
+ login_as(:quentin)
833
+ @proposal = stub_model(Proposal, id: 123)
834
+ @proposal.should_receive(:save).and_return(true)
835
+ @proposal.should_receive(:add_user).and_return(true)
836
+ Proposal.should_receive(:new).and_return(@proposal)
837
+ end
838
+
839
+ it "should display success page" do
840
+ @controller.should_receive(:render).and_return("My HTML here")
841
+
842
+ post :create, commit: "Create", proposal: {foo: 'bar'}
843
+ end
844
+ end
845
+
846
+ end
847
+
848
+ describe "update" do
849
+ def assert_update(login=nil, inputs={}, optional_params={}, &block)
850
+ login ? login_as(login) : logout
851
+ optional_params.reverse_merge commit: 'really'
852
+ put :update, { id: ( inputs['id'] || inputs[:id] ), proposal: inputs }.merge(optional_params)
853
+ block.call
854
+ end
855
+
856
+ before do
857
+ @user = users(:quentin)
858
+ @proposal = proposals(:quentin_widgets)
859
+ @inputs = @proposal.attributes
860
+ end
861
+
862
+ it "should prevent editing of title when proposal titles are locked" do
863
+ @event = stub_current_event!
864
+ @event.stub(:proposal_titles_locked?).and_return(true)
865
+ @controller.stub(:get_proposal_and_assignment_status).and_return(@proposal)
866
+ @proposal.stub(:event).and_return(@event)
867
+
868
+ assert_update(:quentin, id: @proposal.id, title: 'OMG') do
869
+ @proposal.reload
870
+ @proposal.title.should_not == 'OMG'
871
+ end
872
+ end
873
+
874
+ it "should redirect anonymous user to login" do
875
+ assert_update(nil, @inputs) do
876
+ response.should redirect_to(sign_in_path)
877
+ end
878
+ end
879
+
880
+ it "should reject non-owner mortal user" do
881
+ assert_update(:clio, @inputs) do
882
+ flash.keys.should include(:failure)
883
+ response.should redirect_to(proposal_url(@proposal))
884
+ end
885
+ end
886
+
887
+ describe "when settings status" do
888
+ it "should allow admin to change status" do
889
+ @inputs[:transition] = 'accept'
890
+ @controller.should_receive(:get_proposal_and_assignment_status).and_return([@proposal, :assigned_via_param])
891
+ @proposal.should_receive(:accept!)
892
+ assert_update(:aaron, @inputs) do
893
+ # Everything is done through the should_receive
894
+ end
895
+ end
896
+
897
+ it "should not allow non-admin to change status" do
898
+ @inputs[:transition] = 'accept'
899
+ @controller.should_receive(:get_proposal_and_assignment_status).and_return([@proposal, :assigned_via_param])
900
+ @proposal.should_not_receive(:accept!)
901
+ assert_update(:quentin, @inputs) do
902
+ end
903
+ end
904
+ end
905
+
906
+ describe "with user_profiles?" do
907
+ before(:each) do
908
+ OpenConferenceWare.stub(have_user_profiles: true)
909
+ end
910
+
911
+ it "should specify update behavior"
912
+ end
913
+
914
+ describe "without user_profiles?" do
915
+ before(:each) do
916
+ OpenConferenceWare.stub(have_user_profiles: false)
917
+ end
918
+
919
+ it "should display edit form if fields are invalid" do
920
+ inputs = @inputs.clone
921
+ inputs['presenter'] = nil
922
+ assert_update(:quentin, inputs) do
923
+ response.should be_success
924
+ response.should render_template('edit')
925
+ end
926
+ end
927
+
928
+ it "should allow owner mortal user" do
929
+ assert_update(:quentin, @inputs) do
930
+ flash.keys.should include(:success)
931
+ response.should redirect_to(proposal_url(@proposal))
932
+ end
933
+ end
934
+
935
+ it "should display preview" do
936
+ assert_update(:quentin, @inputs, { commit: nil, preview: 'Preview' }) do
937
+ response.should be_success
938
+ response.should render_template('edit')
939
+ end
940
+ end
941
+
942
+ it "should allow admin user" do
943
+ assert_update(:aaron, @inputs) do
944
+ flash.keys.should include(:success)
945
+ response.should redirect_to(proposal_url(@proposal))
946
+ end
947
+ end
948
+ end
949
+ end
950
+
951
+ describe "delete" do
952
+ before do
953
+ @proposal = proposals(:quentin_widgets)
954
+ Proposal.stub(:lookup).and_return(@proposal)
955
+ end
956
+
957
+ def assert_delete(login=nil, &block)
958
+ login ? login_as(login) : logout
959
+ delete :destroy, id: @proposal.id
960
+ block.call
961
+ end
962
+
963
+ it "should ask anonymous to login" do
964
+ @proposal.should_not_receive(:destroy)
965
+ assert_delete do
966
+ response.should redirect_to(sign_in_path)
967
+ end
968
+ end
969
+
970
+ it "should reject non-owner mortal user" do
971
+ @proposal.should_not_receive(:destroy)
972
+ assert_delete(:clio) do
973
+ flash.keys.should include(:failure)
974
+ response.should redirect_to(proposal_url(@proposal))
975
+ end
976
+ end
977
+
978
+ it "should allow owner mortal user" do
979
+ @proposal.should_receive(:destroy)
980
+ assert_delete(:quentin) do
981
+ flash.keys.should include(:success)
982
+ response.should redirect_to(event_proposals_url(@proposal.event))
983
+ end
984
+ end
985
+
986
+ it "should allow admin user" do
987
+ @proposal.should_receive(:destroy)
988
+ assert_delete(:quentin) do
989
+ flash.keys.should include(:success)
990
+ response.should redirect_to(event_proposals_url(@proposal.event))
991
+ end
992
+ end
993
+ end
994
+
995
+ describe "schedule" do
996
+ it "should not fail like a whale" do
997
+ @controller.stub(:schedule_visible?).and_return(true)
998
+ item = proposals(:postgresql_session)
999
+
1000
+ get :schedule, event_id: @event.slug
1001
+
1002
+ response.should be_success
1003
+ response.body.should have_selector(".summary", text: item.title)
1004
+ end
1005
+
1006
+ it "should not fail like a whale with iCalendar" do
1007
+ @controller.stub(:schedule_visible?).and_return(true)
1008
+ item = proposals(:postgresql_session)
1009
+
1010
+ get :schedule, event_id: @event.slug, format: "ics"
1011
+
1012
+ response.should be_success
1013
+ calendar = Vpim::Icalendar.decode(response.body).first
1014
+ component = calendar.find{|t| t.summary == item.title}
1015
+
1016
+ dtstart = Time.parse(component.dtstart.strftime('%Y-%m-%d %H:%M:%S UTC'))
1017
+ dtend = Time.parse(component.dtend.strftime('%Y-%m-%d %H:%M:%S UTC'))
1018
+
1019
+ component.should_not be_nil
1020
+ dtstart.should == item.start_time
1021
+ dtend.should == item.end_time
1022
+ component.summary.should == item.title
1023
+ component.description.should == (item.respond_to?(:users) ?
1024
+ "#{item.users.map(&:fullname).join(', ')}: #{item.excerpt}" :
1025
+ item.excerpt)
1026
+ component.url == session_url(item)
1027
+ end
1028
+ end
1029
+
1030
+ describe "manage speakers" do
1031
+ before(:each) do
1032
+ OpenConferenceWare.stub(have_user_profiles: true)
1033
+ @bubba = stub_model(User, fullname: "Bubba Smith")
1034
+ @billy = stub_model(User, fullname: "Billy Jack")
1035
+ @sue = stub_model(User, fullname: "Sue Smith")
1036
+ @proposal = stub_model(Proposal)
1037
+ @proposal.users = [@bubba, @billy]
1038
+ @event = stub_current_event!
1039
+ controller.stub(:assign_get_proposal_for_speaker_manager)
1040
+ controller.stub(:get_proposal_for_speaker_manager).and_return(@proposal)
1041
+ end
1042
+
1043
+ it "should list" do
1044
+ get :manage_speakers, speakers: "#{@bubba.id},#{@billy.id}", id: @proposal.to_param
1045
+ response.body.should have_selector(".speaker_id[name='speaker_ids[#{@bubba.id}]']")
1046
+ response.body.should have_selector(".speaker_id[name='speaker_ids[#{@billy.id}]']")
1047
+ response.body.should_not have_selector(".speaker_id[name='speaker_ids[#{@sue.id}]']")
1048
+ end
1049
+
1050
+ it "should add user" do
1051
+ User.should_receive(:find).and_return(@sue)
1052
+ get :manage_speakers, speakers: "#{@bubba.id},#{@billy.id}", add: @sue.id, id: @proposal.to_param
1053
+ response.body.should have_selector(".speaker_id[name='speaker_ids[#{@bubba.id}]']")
1054
+ response.body.should have_selector(".speaker_id[name='speaker_ids[#{@billy.id}]']")
1055
+ response.body.should have_selector(".speaker_id[name='speaker_ids[#{@sue.id}]']")
1056
+ end
1057
+
1058
+ it "should remove user" do
1059
+ User.should_receive(:find).and_return(@billy)
1060
+ get :manage_speakers, speakers: "#{@bubba.id},#{@billy.id}", remove: @billy.id, id: @proposal.to_param
1061
+ response.body.should have_selector(".speaker_id[name='speaker_ids[#{@bubba.id}]']")
1062
+ response.body.should_not have_selector(".speaker_id[name='speaker_ids[#{@billy.id}]']")
1063
+ end
1064
+ end
1065
+
1066
+ describe "search speakers" do
1067
+ before(:each) do
1068
+ @proposal = stub_model(Proposal)
1069
+
1070
+ @bubba = stub_model(User, fullname: "Bubba Smith")
1071
+ @billy = stub_model(User, fullname: "Billy Smith")
1072
+ @john = stub_model(User, fullname: "John Doe")
1073
+
1074
+ @params = {
1075
+ search: "smith",
1076
+ speakers: "IGNORED",
1077
+ }
1078
+
1079
+ User.should_receive(:complete_profiles).and_return([@bubba, @john, @billy])
1080
+ end
1081
+
1082
+ describe "new record" do
1083
+ before(:each) do
1084
+ @params[:id] = "new_record"
1085
+ Proposal.should_receive(:new).and_return(@proposal)
1086
+ @proposal.should_receive(:add_user)
1087
+ end
1088
+
1089
+ it "should match users that aren't in the proposal" do
1090
+ @proposal.should_receive(:users).and_return([])
1091
+ post :search_speakers, @params
1092
+ assigns(:matches).should == [@bubba, @billy]
1093
+ end
1094
+
1095
+ it "should not match users that are in the proposal" do
1096
+ @proposal.should_receive(:users).and_return([@bubba])
1097
+ post :search_speakers, @params
1098
+ assigns(:matches).should == [@billy]
1099
+ end
1100
+ end
1101
+
1102
+ describe "existing record" do
1103
+ before(:each) do
1104
+ @proposal.id = 123
1105
+ @proposal.event = events(:open)
1106
+ @params[:id] = @proposal.id
1107
+ Proposal.stub(:find).and_return(@proposal)
1108
+ end
1109
+
1110
+ it "should match users that aren't in the proposal" do
1111
+ @proposal.should_receive(:users).and_return([])
1112
+ post :search_speakers, @params
1113
+ assigns(:matches).should == [@bubba, @billy]
1114
+ end
1115
+
1116
+ it "should not match users that are in the proposal" do
1117
+ @proposal.should_receive(:users).and_return([@bubba])
1118
+ post :search_speakers, @params
1119
+ assigns(:matches).should == [@billy]
1120
+ end
1121
+ end
1122
+ end
1123
+
1124
+ def assert_confirmed
1125
+ post :speaker_confirm, id: @proposal.id
1126
+ @proposal.reload
1127
+ @proposal.status.should == 'confirmed'
1128
+ flash[:success].should =~ /Updated/
1129
+ end
1130
+
1131
+ def assert_not_confirmed
1132
+ post :speaker_confirm, id: @proposal.id
1133
+ @proposal.reload
1134
+ @proposal.status.should_not == 'confirmed'
1135
+ flash[:success].should_not =~ /Updated/
1136
+ end
1137
+
1138
+ describe "proposal login required" do
1139
+ it "should redirect to login if not logged in" do
1140
+ proposal = proposals(:quentin_widgets)
1141
+ get :proposal_login_required, proposal_id: proposal.id
1142
+ response.should redirect_to(sign_in_path)
1143
+ end
1144
+
1145
+ it "should redirect to proposal if logged in" do
1146
+ login_as(users(:quentin))
1147
+ proposal = proposals(:quentin_widgets)
1148
+ get :proposal_login_required, proposal_id: proposal.id
1149
+ response.should redirect_to(proposal_path(proposal))
1150
+ end
1151
+ end
1152
+
1153
+ describe "speaker confirm" do
1154
+ describe "accepted proposal" do
1155
+ before(:each) do
1156
+ @proposal = proposals(:quentin_widgets)
1157
+ @proposal.accept!
1158
+ end
1159
+
1160
+ it "should confirm for owners of the proposal" do
1161
+ login_as(users(:quentin))
1162
+ assert_confirmed
1163
+ end
1164
+ it "should not confirm for non-owners of the proposal" do
1165
+ login_as(users(:aaron))
1166
+ assert_not_confirmed
1167
+ end
1168
+ end
1169
+
1170
+ describe "not-accepted proposal" do
1171
+ before(:each) do
1172
+ @proposal = proposals(:quentin_widgets)
1173
+ end
1174
+
1175
+ it "should not confirm for owners of the proposal" do
1176
+ login_as(users(:quentin))
1177
+ lambda { post :speaker_confirm, id: @proposal.id }.should raise_error(AASM::InvalidTransition)
1178
+ end
1179
+ it "should not confirm for non-owners of the proposal" do
1180
+ login_as(users(:aaron))
1181
+ assert_not_confirmed
1182
+ end
1183
+ end
1184
+ end
1185
+
1186
+ def assert_declined
1187
+ post :speaker_decline, id: @proposal.id
1188
+ @proposal.reload
1189
+ @proposal.status.should == 'declined'
1190
+ flash[:success].should =~ /Updated/
1191
+ end
1192
+
1193
+ def assert_not_declined
1194
+ post :speaker_decline, id: @proposal.id
1195
+ @proposal.reload
1196
+ @proposal.status.should_not == 'declined'
1197
+ flash[:success].should_not =~ /Updated/
1198
+ end
1199
+
1200
+ describe "speaker decline" do
1201
+ describe "accepted proposal" do
1202
+ before(:each) do
1203
+ @proposal = proposals(:quentin_widgets)
1204
+ @proposal.accept!
1205
+ end
1206
+
1207
+ it "should decline for owners of the proposal" do
1208
+ login_as(users(:quentin))
1209
+ assert_declined
1210
+ end
1211
+ it "should not decline for non-owners of the proposal" do
1212
+ login_as(users(:aaron))
1213
+ assert_not_declined
1214
+ end
1215
+ end
1216
+
1217
+ describe "not-accepted proposal" do
1218
+ before(:each) do
1219
+ @proposal = proposals(:quentin_widgets)
1220
+ end
1221
+
1222
+ it "should not decline for owners of the proposal" do
1223
+ login_as(users(:quentin))
1224
+ lambda { post :speaker_decline, id: @proposal.id }.should raise_error(AASM::InvalidTransition)
1225
+ end
1226
+ it "should not decline for non-owners of the proposal" do
1227
+ login_as(users(:aaron))
1228
+ assert_not_declined
1229
+ end
1230
+ end
1231
+ end
1232
+
1233
+ describe "get_proposal_and_assignment_status" do
1234
+ it "should return a status of :invalid_proposal when no proposal id is given" do
1235
+ @controller.stub(:params).and_return({ id: nil })
1236
+ @controller.send(:get_proposal_and_assignment_status).should == [nil, :invalid_proposal]
1237
+ end
1238
+
1239
+ it "should return a status of :invalid_event when a proposal doesn't have a valid event" do
1240
+ proposal = stub_model(Proposal, state: "confirmed", event: nil)
1241
+ Proposal.stub(:lookup).and_return(proposal)
1242
+ @controller.stub(:params).and_return({ id: 1000 })
1243
+ @controller.send(:get_proposal_and_assignment_status).should == [proposal, :invalid_event]
1244
+ end
1245
+ end
1246
+
1247
+ describe "assign_proposal_and_event" do
1248
+
1249
+ it "should return false and not redirect when proposal and its event are successfully found" do
1250
+ proposal = stub_model(Proposal, state: "confirmed", event: @event)
1251
+ @controller.should_receive(:get_proposal_and_assignment_status).and_return([proposal, :assigned_via_param])
1252
+ @controller.send(:assign_proposal_and_event).should == false
1253
+ flash[:failure].should be_nil
1254
+ end
1255
+
1256
+ it "should redirect when proposal assignment status is :invalid_proposal" do
1257
+ proposal = stub_model(Proposal, event: @event)
1258
+ @controller.should_receive(:get_proposal_and_assignment_status).and_return([proposal, :invalid_proposal])
1259
+ @controller.should_receive(:redirect_to)
1260
+ @controller.send(:assign_proposal_and_event)
1261
+ flash[:failure].should == "Sorry, that presentation proposal doesn't exist or has been deleted."
1262
+ end
1263
+
1264
+ it "should redirect when proposal assignment status is :invalid_event" do
1265
+ proposal = stub_model(Proposal, state: "confirmed", event: nil, id: 1)
1266
+ @controller.should_receive(:get_proposal_and_assignment_status).and_return([proposal, :invalid_event])
1267
+ @controller.should_receive(:redirect_to)
1268
+ @controller.send(:assign_proposal_and_event)
1269
+ flash[:failure].should == "Sorry, no event was associated with proposal #1"
1270
+ end
1271
+ end
1272
+
1273
+ end