calagator 0.0.1.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 (384) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +23 -0
  3. data/README.md +75 -0
  4. data/Rakefile +12 -0
  5. data/app/assets/images/confused-alligator-sm.png +0 -0
  6. data/app/assets/images/disk.png +0 -0
  7. data/app/assets/images/edit.png +0 -0
  8. data/app/assets/images/external_sites/epdx.png +0 -0
  9. data/app/assets/images/external_sites/external.gif +0 -0
  10. data/app/assets/images/external_sites/facebook.png +0 -0
  11. data/app/assets/images/external_sites/foursquare.png +0 -0
  12. data/app/assets/images/external_sites/gowalla.png +0 -0
  13. data/app/assets/images/external_sites/lanyrd.png +0 -0
  14. data/app/assets/images/external_sites/meetup.png +0 -0
  15. data/app/assets/images/external_sites/plancast.png +0 -0
  16. data/app/assets/images/external_sites/shizzow.png +0 -0
  17. data/app/assets/images/external_sites/upcoming.png +0 -0
  18. data/app/assets/images/external_sites/yelp.png +0 -0
  19. data/app/assets/images/feed.png +0 -0
  20. data/app/assets/images/heart.png +0 -0
  21. data/app/assets/images/icon_ical.gif +0 -0
  22. data/app/assets/images/information.png +0 -0
  23. data/app/assets/images/nav_marker.gif +0 -0
  24. data/app/assets/images/nav_marker.png +0 -0
  25. data/app/assets/images/plus.png +0 -0
  26. data/app/assets/images/redx.png +0 -0
  27. data/app/assets/images/site-icon.png +0 -0
  28. data/app/assets/images/spinner.gif +0 -0
  29. data/app/assets/images/star.png +0 -0
  30. data/app/assets/images/subnav_marker.gif +0 -0
  31. data/app/assets/images/subnav_marker.png +0 -0
  32. data/app/assets/images/tag_blue.png +0 -0
  33. data/app/assets/images/tag_icons/angular.png +0 -0
  34. data/app/assets/images/tag_icons/beer.png +0 -0
  35. data/app/assets/images/tag_icons/bitcoin.png +0 -0
  36. data/app/assets/images/tag_icons/free.png +0 -0
  37. data/app/assets/images/tag_icons/golang.png +0 -0
  38. data/app/assets/images/tag_icons/html.png +0 -0
  39. data/app/assets/images/tag_icons/javascript.png +0 -0
  40. data/app/assets/images/tag_icons/linux.png +0 -0
  41. data/app/assets/images/tag_icons/php.png +0 -0
  42. data/app/assets/images/tag_icons/pizza.png +0 -0
  43. data/app/assets/images/tag_icons/python.png +0 -0
  44. data/app/assets/images/tag_icons/rails.png +0 -0
  45. data/app/assets/images/tag_icons/ruby.png +0 -0
  46. data/app/assets/images/tag_icons/sass.png +0 -0
  47. data/app/assets/images/tag_icons/swift.png +0 -0
  48. data/app/assets/images/transmit_blue.png +0 -0
  49. data/app/assets/images/weekday_background.gif +0 -0
  50. data/app/assets/javascripts/calagator.js +18 -0
  51. data/app/assets/javascripts/calagator/forms.js +60 -0
  52. data/app/assets/stylesheets/calagator.scss +8 -0
  53. data/app/assets/stylesheets/calagator/common.scss +14 -0
  54. data/app/assets/stylesheets/calagator/datepicker.scss +177 -0
  55. data/app/assets/stylesheets/calagator/errors.css +110 -0
  56. data/app/assets/stylesheets/calagator/forms.scss +4 -0
  57. data/app/assets/stylesheets/calagator/reset.css +40 -0
  58. data/app/assets/stylesheets/calagator/theme.css +0 -0
  59. data/app/controllers/calagator/admin_controller.rb +27 -0
  60. data/app/controllers/calagator/application_controller.rb +82 -0
  61. data/app/controllers/calagator/events_controller.rb +171 -0
  62. data/app/controllers/calagator/site_controller.rb +35 -0
  63. data/app/controllers/calagator/sources_controller.rb +94 -0
  64. data/app/controllers/calagator/venues_controller.rb +133 -0
  65. data/app/controllers/calagator/versions_controller.rb +20 -0
  66. data/app/helpers/calagator/application_helper.rb +128 -0
  67. data/app/helpers/calagator/events_helper.rb +184 -0
  68. data/app/helpers/calagator/google_event_export_helper.rb +67 -0
  69. data/app/helpers/calagator/mapping_helper.rb +103 -0
  70. data/app/helpers/calagator/sources_helper.rb +10 -0
  71. data/app/helpers/calagator/tags_helper.rb +42 -0
  72. data/app/helpers/calagator/time_range_helper.rb +166 -0
  73. data/app/models/calagator/event.rb +246 -0
  74. data/app/models/calagator/event/cloner.rb +39 -0
  75. data/app/models/calagator/event/ical_renderer.rb +155 -0
  76. data/app/models/calagator/event/overview.rb +45 -0
  77. data/app/models/calagator/event/saver.rb +58 -0
  78. data/app/models/calagator/event/search.rb +72 -0
  79. data/app/models/calagator/event/search_engine.rb +28 -0
  80. data/app/models/calagator/event/search_engine/apache_sunspot.rb +106 -0
  81. data/app/models/calagator/event/search_engine/sql.rb +107 -0
  82. data/app/models/calagator/source.rb +115 -0
  83. data/app/models/calagator/source/importer.rb +46 -0
  84. data/app/models/calagator/source/parser.rb +128 -0
  85. data/app/models/calagator/source/parser/facebook.rb +67 -0
  86. data/app/models/calagator/source/parser/hcal.rb +108 -0
  87. data/app/models/calagator/source/parser/http_authentication_required_error.rb +6 -0
  88. data/app/models/calagator/source/parser/ical.rb +186 -0
  89. data/app/models/calagator/source/parser/meetup.rb +72 -0
  90. data/app/models/calagator/source/parser/not_found.rb +9 -0
  91. data/app/models/calagator/source/parser/plancast.rb +59 -0
  92. data/app/models/calagator/venue.rb +161 -0
  93. data/app/models/calagator/venue/geocoder.rb +51 -0
  94. data/app/models/calagator/venue/search.rb +63 -0
  95. data/app/models/calagator/venue/search_engine.rb +24 -0
  96. data/app/models/calagator/venue/search_engine/apache_sunspot.rb +85 -0
  97. data/app/models/calagator/venue/search_engine/sql.rb +68 -0
  98. data/app/models/event/search_engine/base.rb +0 -0
  99. data/app/observers/calagator/cache_observer.rb +37 -0
  100. data/app/views/calagator/admin/events.html.erb +28 -0
  101. data/app/views/calagator/admin/index.html.erb +8 -0
  102. data/app/views/calagator/events/_feed_item.html.erb +62 -0
  103. data/app/views/calagator/events/_form.html.erb +63 -0
  104. data/app/views/calagator/events/_gcal_reminder.html.erb +1 -0
  105. data/app/views/calagator/events/_hcal.html.erb +41 -0
  106. data/app/views/calagator/events/_item.html.erb +120 -0
  107. data/app/views/calagator/events/_list.html.erb +30 -0
  108. data/app/views/calagator/events/_list_item.html.erb +37 -0
  109. data/app/views/calagator/events/_search_section.html.erb +15 -0
  110. data/app/views/calagator/events/_subnav.html.erb +13 -0
  111. data/app/views/calagator/events/_table.html.erb +74 -0
  112. data/app/views/calagator/events/duplicates.html.erb +58 -0
  113. data/app/views/calagator/events/edit.html.erb +3 -0
  114. data/app/views/calagator/events/index.atom.builder +32 -0
  115. data/app/views/calagator/events/index.html.erb +51 -0
  116. data/app/views/calagator/events/index.kml.erb +20 -0
  117. data/app/views/calagator/events/new.html.erb +7 -0
  118. data/app/views/calagator/events/search.html.erb +25 -0
  119. data/app/views/calagator/events/show.html.erb +81 -0
  120. data/app/views/calagator/site/_appropriateness_message.html.erb +15 -0
  121. data/app/views/calagator/site/_description.html.erb +3 -0
  122. data/app/views/calagator/site/about.html.erb +7 -0
  123. data/app/views/calagator/site/defunct.html.erb +15 -0
  124. data/app/views/calagator/site/export.html.erb +4 -0
  125. data/app/views/calagator/site/index.html.erb +40 -0
  126. data/app/views/calagator/site/opensearch.xml.builder +8 -0
  127. data/app/views/calagator/sources/_subnav.html.erb +0 -0
  128. data/app/views/calagator/sources/edit.html.erb +12 -0
  129. data/app/views/calagator/sources/import.html.erb +10 -0
  130. data/app/views/calagator/sources/index.html.erb +22 -0
  131. data/app/views/calagator/sources/new.html.erb +32 -0
  132. data/app/views/calagator/sources/show.html.erb +16 -0
  133. data/app/views/calagator/venues/_form.html.erb +57 -0
  134. data/app/views/calagator/venues/_subnav.html.erb +10 -0
  135. data/app/views/calagator/venues/duplicates.html.erb +65 -0
  136. data/app/views/calagator/venues/edit.html.erb +3 -0
  137. data/app/views/calagator/venues/index.html.erb +93 -0
  138. data/app/views/calagator/venues/index.kml.erb +15 -0
  139. data/app/views/calagator/venues/map.html.erb +4 -0
  140. data/app/views/calagator/venues/new.html.erb +5 -0
  141. data/app/views/calagator/venues/show.html.erb +113 -0
  142. data/app/views/calagator/versions/_chooser.html.erb +28 -0
  143. data/app/views/calagator/versions/_edit_with_chooser.html.erb +16 -0
  144. data/app/views/layouts/calagator/application.html.erb +110 -0
  145. data/config/deploy/local.rb +37 -0
  146. data/config/deploy/lucca.rb +33 -0
  147. data/config/initializers/dates.rb +7 -0
  148. data/config/initializers/formtastic.rb +82 -0
  149. data/config/initializers/geokit.rb +74 -0
  150. data/config/initializers/ics_renderer.rb +6 -0
  151. data/config/initializers/load_tag_model_extensions.rb +3 -0
  152. data/config/initializers/mime_types.rb +9 -0
  153. data/config/initializers/observers.rb +1 -0
  154. data/config/initializers/search_engine.rb +4 -0
  155. data/config/initializers/set_default_url_host.rb +5 -0
  156. data/config/initializers/time_get_zone.rb +8 -0
  157. data/config/locales/en.yml +5 -0
  158. data/config/routes.rb +55 -0
  159. data/config/secrets.yml.blag +75 -0
  160. data/config/sunspot.yml +23 -0
  161. data/db/development.sqlite3 +0 -0
  162. data/db/development.sqlite3.bak +0 -0
  163. data/db/development.sqlite3.old +0 -0
  164. data/db/development~20111112@110950.sqlite3 +0 -0
  165. data/db/migrate/001_create_events.rb +17 -0
  166. data/db/migrate/002_create_venues.rb +17 -0
  167. data/db/migrate/003_create_sources.rb +16 -0
  168. data/db/migrate/004_add_detailed_fields_to_venue.rb +19 -0
  169. data/db/migrate/005_add_end_time_to_events.rb +9 -0
  170. data/db/migrate/006_add_source_id_to_events.rb +9 -0
  171. data/db/migrate/008_add_source_id_to_venues.rb +10 -0
  172. data/db/migrate/009_add_duplicate_of_column_to_venues.rb +9 -0
  173. data/db/migrate/010_add_duplicate_of_column_to_events.rb +9 -0
  174. data/db/migrate/011_change_lat_long_type.rb +12 -0
  175. data/db/migrate/012_add_source_reimport.rb +9 -0
  176. data/db/migrate/013_change_end_time_to_duration.rb +11 -0
  177. data/db/migrate/014_remove_format_type_from_source.rb +9 -0
  178. data/db/migrate/015_create_updates.rb +15 -0
  179. data/db/migrate/016_remove_next_update_from_source.rb +9 -0
  180. data/db/migrate/20080705163959_change_duration_to_end_time.rb +11 -0
  181. data/db/migrate/20080705164959_create_tags_and_taggings.rb +28 -0
  182. data/db/migrate/20081011181519_create_versioned_events.rb +25 -0
  183. data/db/migrate/20081011193124_create_versioned_venues.rb +32 -0
  184. data/db/migrate/20081115190515_add_rrule_to_events.rb +15 -0
  185. data/db/migrate/20090912082129_create_versions.rb +18 -0
  186. data/db/migrate/20110219205156_add_closed_flag_to_venues.rb +9 -0
  187. data/db/migrate/20110220001008_add_wifi_flag_to_venues.rb +9 -0
  188. data/db/migrate/20110220011427_add_access_notes_to_venues.rb +9 -0
  189. data/db/migrate/20110220031117_add_events_count_to_venues.rb +8 -0
  190. data/db/migrate/20110604174521_add_venue_details_to_events.rb +9 -0
  191. data/db/migrate/20110717231316_acts_as_taggable_on_migration.rb +50 -0
  192. data/db/migrate/20120709092821_cleanup.rb +14 -0
  193. data/db/migrate/20120831234448_specify_venues_latitude_and_longitude_precision.rb +11 -0
  194. data/db/migrate/20150206085809_remove_updates.rb +13 -0
  195. data/db/migrate/20150207231355_add_locked_status_to_events.rb +5 -0
  196. data/db/production.sqlite3 +0 -0
  197. data/db/schema.rb +102 -0
  198. data/db/seeds.rb +16 -0
  199. data/db/test.sqlite3 +0 -0
  200. data/db/test2.sqlite3 +0 -0
  201. data/lib/calagator.rb +30 -0
  202. data/lib/calagator/blacklist_validator.rb +69 -0
  203. data/lib/calagator/decode_html_entities_hack.rb +33 -0
  204. data/lib/calagator/duplicate_checking.rb +133 -0
  205. data/lib/calagator/duplicate_checking/controller_actions.rb +38 -0
  206. data/lib/calagator/duplicate_checking/duplicate_finder.rb +83 -0
  207. data/lib/calagator/duplicate_checking/duplicate_squasher.rb +74 -0
  208. data/lib/calagator/engine.rb +30 -0
  209. data/lib/calagator/strip_whitespace.rb +19 -0
  210. data/lib/calagator/tag_model_extensions.rb +104 -0
  211. data/lib/calagator/url_prefixer.rb +9 -0
  212. data/lib/calagator/version.rb +3 -0
  213. data/lib/generators/calagator/install_generator.rb +39 -0
  214. data/lib/generators/calagator/templates/config/calagator.rb +26 -0
  215. data/lib/generators/calagator/templates/config/secrets.yml.sample +83 -0
  216. data/lib/secrets_reader.rb +76 -0
  217. data/lib/tasks/spec_db.rake +53 -0
  218. data/lib/tasks/sunspot_reindex_calagator.rake +6 -0
  219. data/lib/tasks/sunspot_solr_restart_enhancements.rake +19 -0
  220. data/lib/tasks/update_counter_caches.rake +13 -0
  221. data/lib/templates/erb/scaffold/_form.html.erb +11 -0
  222. data/lib/theme_reader.rb +17 -0
  223. data/lib/wait_for_solr.rb +25 -0
  224. data/spec/controllers/calagator/application_controller_spec.rb +47 -0
  225. data/spec/controllers/calagator/events_controller_spec.rb +794 -0
  226. data/spec/controllers/calagator/site_controller_spec.rb +59 -0
  227. data/spec/controllers/calagator/sources_controller_spec.rb +439 -0
  228. data/spec/controllers/calagator/venues_controller_spec.rb +319 -0
  229. data/spec/controllers/calagator/versions_controller_spec.rb +82 -0
  230. data/spec/controllers/squash_many_duplicates_examples.rb +49 -0
  231. data/spec/dummy/Gemfile +39 -0
  232. data/spec/dummy/Gemfile.lock +195 -0
  233. data/spec/dummy/README.rdoc +261 -0
  234. data/spec/dummy/Rakefile +7 -0
  235. data/spec/dummy/app/assets/images/rails.png +0 -0
  236. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  237. data/spec/dummy/app/assets/stylesheets/application.css +14 -0
  238. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  239. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  240. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  241. data/spec/dummy/config.ru +4 -0
  242. data/spec/dummy/config/application.rb +62 -0
  243. data/spec/dummy/config/boot.rb +6 -0
  244. data/spec/dummy/config/database.yml +25 -0
  245. data/spec/dummy/config/environment.rb +5 -0
  246. data/spec/dummy/config/environments/development.rb +37 -0
  247. data/spec/dummy/config/environments/production.rb +67 -0
  248. data/spec/dummy/config/environments/test.rb +37 -0
  249. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  250. data/spec/dummy/config/initializers/calagator.rb +26 -0
  251. data/spec/dummy/config/initializers/inflections.rb +15 -0
  252. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  253. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  254. data/spec/dummy/config/initializers/session_store.rb +8 -0
  255. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  256. data/spec/dummy/config/locales/en.yml +5 -0
  257. data/spec/dummy/config/routes.rb +59 -0
  258. data/spec/dummy/config/secrets.yml +83 -0
  259. data/spec/dummy/db/development.sqlite3 +0 -0
  260. data/spec/dummy/db/migrate/20150309023304_create_events.calagator.rb +18 -0
  261. data/spec/dummy/db/migrate/20150309023305_create_venues.calagator.rb +18 -0
  262. data/spec/dummy/db/migrate/20150309023306_create_sources.calagator.rb +17 -0
  263. data/spec/dummy/db/migrate/20150309023307_add_detailed_fields_to_venue.calagator.rb +20 -0
  264. data/spec/dummy/db/migrate/20150309023308_add_end_time_to_events.calagator.rb +10 -0
  265. data/spec/dummy/db/migrate/20150309023309_add_source_id_to_events.calagator.rb +10 -0
  266. data/spec/dummy/db/migrate/20150309023310_add_source_id_to_venues.calagator.rb +11 -0
  267. data/spec/dummy/db/migrate/20150309023311_add_duplicate_of_column_to_venues.calagator.rb +10 -0
  268. data/spec/dummy/db/migrate/20150309023312_add_duplicate_of_column_to_events.calagator.rb +10 -0
  269. data/spec/dummy/db/migrate/20150309023313_change_lat_long_type.calagator.rb +13 -0
  270. data/spec/dummy/db/migrate/20150309023314_add_source_reimport.calagator.rb +10 -0
  271. data/spec/dummy/db/migrate/20150309023315_change_end_time_to_duration.calagator.rb +12 -0
  272. data/spec/dummy/db/migrate/20150309023316_remove_format_type_from_source.calagator.rb +10 -0
  273. data/spec/dummy/db/migrate/20150309023317_create_updates.calagator.rb +16 -0
  274. data/spec/dummy/db/migrate/20150309023318_remove_next_update_from_source.calagator.rb +10 -0
  275. data/spec/dummy/db/migrate/20150309023319_change_duration_to_end_time.calagator.rb +12 -0
  276. data/spec/dummy/db/migrate/20150309023320_create_tags_and_taggings.calagator.rb +29 -0
  277. data/spec/dummy/db/migrate/20150309023321_create_versioned_events.calagator.rb +26 -0
  278. data/spec/dummy/db/migrate/20150309023322_create_versioned_venues.calagator.rb +33 -0
  279. data/spec/dummy/db/migrate/20150309023323_add_rrule_to_events.calagator.rb +16 -0
  280. data/spec/dummy/db/migrate/20150309023324_create_versions.calagator.rb +19 -0
  281. data/spec/dummy/db/migrate/20150309023325_add_closed_flag_to_venues.calagator.rb +10 -0
  282. data/spec/dummy/db/migrate/20150309023326_add_wifi_flag_to_venues.calagator.rb +10 -0
  283. data/spec/dummy/db/migrate/20150309023327_add_access_notes_to_venues.calagator.rb +10 -0
  284. data/spec/dummy/db/migrate/20150309023328_add_events_count_to_venues.calagator.rb +9 -0
  285. data/spec/dummy/db/migrate/20150309023329_add_venue_details_to_events.calagator.rb +10 -0
  286. data/spec/dummy/db/migrate/20150309023330_acts_as_taggable_on_migration.calagator.rb +51 -0
  287. data/spec/dummy/db/migrate/20150309023331_cleanup.calagator.rb +15 -0
  288. data/spec/dummy/db/migrate/20150309023332_specify_venues_latitude_and_longitude_precision.calagator.rb +12 -0
  289. data/spec/dummy/db/migrate/20150309023333_remove_updates.calagator.rb +14 -0
  290. data/spec/dummy/db/migrate/20150309023334_add_locked_status_to_events.calagator.rb +6 -0
  291. data/spec/dummy/db/schema.rb +95 -0
  292. data/spec/dummy/db/seeds.rb +7 -0
  293. data/spec/dummy/db/test.sqlite3 +0 -0
  294. data/spec/dummy/doc/README_FOR_APP +2 -0
  295. data/spec/dummy/log/development.log +273 -0
  296. data/spec/dummy/public/404.html +26 -0
  297. data/spec/dummy/public/422.html +26 -0
  298. data/spec/dummy/public/500.html +25 -0
  299. data/spec/dummy/public/favicon.ico +0 -0
  300. data/spec/dummy/public/robots.txt +5 -0
  301. data/spec/dummy/script/rails +6 -0
  302. data/spec/dummy/test/performance/browsing_test.rb +12 -0
  303. data/spec/dummy/test/test_helper.rb +13 -0
  304. data/spec/factories.rb +93 -0
  305. data/spec/features/add_event_spec.rb +99 -0
  306. data/spec/features/add_venue_spec.rb +34 -0
  307. data/spec/features/admin_auth_spec.rb +22 -0
  308. data/spec/features/admin_lock_event_spec.rb +41 -0
  309. data/spec/features/import_events_from_feed_spec.rb +43 -0
  310. data/spec/features/managing_event_spec.rb +111 -0
  311. data/spec/features/managing_venue_spec.rb +71 -0
  312. data/spec/features/search_event_spec.rb +27 -0
  313. data/spec/helpers/calagator/application_helper_spec.rb +82 -0
  314. data/spec/helpers/calagator/events_helper_spec.rb +172 -0
  315. data/spec/helpers/calagator/google_event_export_helper_spec.rb +70 -0
  316. data/spec/helpers/calagator/sources_helper_spec.rb +12 -0
  317. data/spec/helpers/calagator/tags_helper_spec.rb +85 -0
  318. data/spec/helpers/calagator/time_range_helper_spec.rb +59 -0
  319. data/spec/lib/calagator/blacklist_validator_spec.rb +65 -0
  320. data/spec/lib/calagator/decode_html_entities_hack_spec.rb +54 -0
  321. data/spec/lib/calagator/settings_spec.rb +20 -0
  322. data/spec/lib/calagator/url_prefixer_spec.rb +33 -0
  323. data/spec/lib/secrets_reader_spec.rb +65 -0
  324. data/spec/models/calagator/event/cloner_spec.rb +43 -0
  325. data/spec/models/calagator/event/overview_spec.rb +79 -0
  326. data/spec/models/calagator/event/search_spec.rb +103 -0
  327. data/spec/models/calagator/event_search_spec.rb +149 -0
  328. data/spec/models/calagator/event_spec.rb +859 -0
  329. data/spec/models/calagator/source/parser_facebook_spec.rb +73 -0
  330. data/spec/models/calagator/source/parser_hcal_spec.rb +69 -0
  331. data/spec/models/calagator/source/parser_ical_non_standard_spec.rb +91 -0
  332. data/spec/models/calagator/source/parser_ical_spec.rb +322 -0
  333. data/spec/models/calagator/source/parser_meetup_spec.rb +69 -0
  334. data/spec/models/calagator/source/parser_plancast_spec.rb +53 -0
  335. data/spec/models/calagator/source/parser_spec.rb +238 -0
  336. data/spec/models/calagator/source_spec.rb +135 -0
  337. data/spec/models/calagator/venue/search_spec.rb +92 -0
  338. data/spec/models/calagator/venue_search_spec.rb +124 -0
  339. data/spec/models/calagator/venue_spec.rb +346 -0
  340. data/spec/models/tag_spec.rb +35 -0
  341. data/spec/rails_helper.rb +39 -0
  342. data/spec/spec_helper.rb +140 -0
  343. data/spec/support/disable_geocoding.rb +1 -0
  344. data/spec/support/http_samples.rb +5 -0
  345. data/spec/support/samples/facebook.json +23 -0
  346. data/spec/support/samples/hcal_basic.xml +6 -0
  347. data/spec/support/samples/hcal_dup_event_dup_venue.xml +13 -0
  348. data/spec/support/samples/hcal_event_duplicates_fixture.xml +13 -0
  349. data/spec/support/samples/hcal_event_wo_lat_and_long.xml +14 -0
  350. data/spec/support/samples/hcal_multiple.xml +16 -0
  351. data/spec/support/samples/hcal_same_event_twice_with_different_venues.xml +12 -0
  352. data/spec/support/samples/hcal_single.xml +8 -0
  353. data/spec/support/samples/hcal_two_identical_events.xml +14 -0
  354. data/spec/support/samples/hcal_upcoming_v1.html +412 -0
  355. data/spec/support/samples/hcal_upcoming_v2.html +749 -0
  356. data/spec/support/samples/hcal_upcoming_v3.html +685 -0
  357. data/spec/support/samples/hcal_upcoming_v4.html +761 -0
  358. data/spec/support/samples/ical_apple.ics +22 -0
  359. data/spec/support/samples/ical_apple_v3.ics +37 -0
  360. data/spec/support/samples/ical_basic.ics +15 -0
  361. data/spec/support/samples/ical_basic_with_duration.ics +16 -0
  362. data/spec/support/samples/ical_event_with_squashed_venue.ics +12 -0
  363. data/spec/support/samples/ical_eventful_many.ics +504 -0
  364. data/spec/support/samples/ical_gmt.ics +12 -0
  365. data/spec/support/samples/ical_google.ics +786 -0
  366. data/spec/support/samples/ical_multiple_calendars.ics +111 -0
  367. data/spec/support/samples/ical_upcoming.ics +36 -0
  368. data/spec/support/samples/ical_upcoming_many.ics +682 -0
  369. data/spec/support/samples/ical_upcoming_v2.ics +43 -0
  370. data/spec/support/samples/ical_z.ics +10 -0
  371. data/spec/support/samples/meetup.ics +16 -0
  372. data/spec/support/samples/meetup.json +31 -0
  373. data/spec/support/samples/plancast.ics +59 -0
  374. data/spec/support/samples/plancast.json +51 -0
  375. data/spec/support/samples/plancast_with_missing_venue.json +39 -0
  376. data/spec/support/samples/upcoming_v1.xml +8 -0
  377. data/spec/support/samples/upcoming_v2_with_invalid_utc_dates.xml +8 -0
  378. data/spec/support/time_convenience_methods.rb +8 -0
  379. data/spec/support/time_zones.rb +12 -0
  380. data/spec/support/url_helpers.rb +5 -0
  381. data/spec/support/wait_for_ajax.rb +15 -0
  382. data/spec/support/webmock.rb +8 -0
  383. data/spec/travis_spec.rb +7 -0
  384. metadata +1194 -0
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ module Calagator
4
+
5
+ describe Event::Cloner do
6
+ describe "when cloning" do
7
+ let :original do
8
+ FactoryGirl.build(:event,
9
+ :id => 42,
10
+ :start_time => Time.zone.parse("2008-01-19 10:00:00"),
11
+ :end_time => Time.zone.parse("2008-01-19 17:00:00"),
12
+ :tag_list => "foo, bar, baz",
13
+ :venue_details => "Details")
14
+ end
15
+
16
+ subject do
17
+ Event::Cloner.clone(original)
18
+ end
19
+
20
+ it { should be_new_record }
21
+ its(:id) { should be_nil }
22
+
23
+ describe "#start_time" do
24
+ it "should equal todays date with the same time" do
25
+ start_time = Date.today + original.start_time.hour.hours
26
+ subject.start_time.should == start_time
27
+ end
28
+ end
29
+
30
+ describe "#end_time" do
31
+ it "should equal todays date with the same time" do
32
+ end_time = Date.today + original.end_time.hour.hours
33
+ subject.end_time.should == end_time
34
+ end
35
+ end
36
+
37
+ %w[title description url venue_id venue_details tag_list].each do |field|
38
+ its(field) { should eq original.send(field) }
39
+ end
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ module Calagator
4
+
5
+ describe Event::Overview, :type => :model do
6
+ describe "#times_to_events" do
7
+ before do
8
+ @today_midnight = today
9
+ @yesterday = @today_midnight.yesterday
10
+ @tomorrow = @today_midnight.tomorrow
11
+ @day_after_tomorrow = @tomorrow.tomorrow
12
+ end
13
+
14
+ describe "#today" do
15
+ it "should include events that started before today and end after today" do
16
+ event = FactoryGirl.create(:event, start_time: @yesterday, end_time: @tomorrow)
17
+ expect(subject.today).to include event
18
+ end
19
+
20
+ it "should include events that started earlier today" do
21
+ event = FactoryGirl.create(:event, start_time: @today_midnight)
22
+ expect(subject.today).to include event
23
+ end
24
+
25
+ it "should not include events that ended before today" do
26
+ event = FactoryGirl.create(:event, start_time: @yesterday, end_time: @yesterday.end_of_day)
27
+ expect(subject.today).not_to include event
28
+ end
29
+
30
+ it "should not include events that start tomorrow" do
31
+ event = FactoryGirl.create(:event, start_time: @tomorrow)
32
+ expect(subject.today).not_to include event
33
+ end
34
+
35
+ it "should not include events that ended at midnight today" do
36
+ event = FactoryGirl.create(:event, start_time: @yesterday, end_time: @today_midnight)
37
+ expect(subject.today).not_to include event
38
+ end
39
+ end
40
+
41
+ describe "#tomorrow" do
42
+ it "should include events that start tomorrow" do
43
+ event = FactoryGirl.create(:event, start_time: @tomorrow)
44
+ expect(subject.tomorrow).to include event
45
+ end
46
+
47
+ it "should not include events that start after tomorrow" do
48
+ event = FactoryGirl.create(:event, start_time: @day_after_tomorrow)
49
+ expect(subject.tomorrow).not_to include event
50
+ end
51
+ end
52
+
53
+ describe "#later" do
54
+ it "should include events that start after tomorrow" do
55
+ event = FactoryGirl.create(:event, start_time: @day_after_tomorrow)
56
+ expect(subject.later).to include event
57
+ end
58
+
59
+ it "should not include events that start after two weeks" do
60
+ event = FactoryGirl.create(:event, start_time: 2.weeks.from_now)
61
+ expect(subject.later).not_to include event
62
+ end
63
+ end
64
+
65
+ describe "#more" do
66
+ it "should provide an event if there are events past the future cutoff" do
67
+ event = FactoryGirl.create(:event, start_time: 2.weeks.from_now)
68
+ expect(subject.more).to eq(event)
69
+ end
70
+
71
+ it "should be nil if there are no events past the future cutoff" do
72
+ event = FactoryGirl.create(:event, start_time: 2.weeks.from_now - 1.day)
73
+ expect(subject.more).to be_blank
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ module Calagator
4
+
5
+ describe Event::Search, :type => :model do
6
+ describe "by keyword" do
7
+ it "should be able to only return events that include a specific keyword" do
8
+ events = double
9
+ expect(Event).to receive(:search).with("myquery", skip_old: false, order: "date").and_return(events)
10
+
11
+ subject = Event::Search.new query: "myquery"
12
+ expect(subject.events).to eq(events)
13
+ end
14
+
15
+ it "should be able to only return current events" do
16
+ events = double
17
+ expect(Event).to receive(:search).with("myquery", order: "date", skip_old: true).and_return(events)
18
+
19
+ subject = Event::Search.new query: "myquery", current: "1"
20
+ expect(subject.events).to eq(events)
21
+ end
22
+
23
+ it "should warn if user tries ordering by invalid order" do
24
+ subject = Event::Search.new query: "myquery", order: "kittens"
25
+ expect(subject.failure_message).to eq("Unknown ordering option \"kittens\", sorting by date instead.")
26
+ expect(subject).not_to be_hard_failure
27
+ end
28
+ end
29
+
30
+ describe "by tag" do
31
+ it "should be able to only return events matching specific tag" do
32
+ events = double
33
+ expect(Event).to receive(:search_tag).with("foo", current: false, order: "date").and_return(events)
34
+
35
+ subject = Event::Search.new tag: "foo"
36
+ expect(subject.events).to eq(events)
37
+ end
38
+
39
+ it "should warn if user tries ordering by invalid order" do
40
+ subject = Event::Search.new tag: "omg", order: "kittens"
41
+ expect(subject.failure_message).to eq("Unknown ordering option \"kittens\", sorting by date instead.")
42
+ expect(subject).not_to be_hard_failure
43
+ end
44
+
45
+ it "should warn if user tries ordering tags by score" do
46
+ subject = Event::Search.new tag: "omg", order: "score"
47
+ expect(subject.failure_message).to eq("You cannot sort tags by score")
48
+ expect(subject).not_to be_hard_failure
49
+ end
50
+ end
51
+
52
+ describe "#grouped_events" do
53
+ it "groups events into a hash by currentness" do
54
+ past_event = double(:event, current?: false)
55
+ current_event = double(:event, current?: true)
56
+ events = [past_event, current_event]
57
+ expect(Event).to receive(:search).and_return(events)
58
+
59
+ expect(subject.grouped_events).to eq({
60
+ past: [past_event],
61
+ current: [current_event],
62
+ })
63
+ end
64
+
65
+ it "discards past events when passed the current option" do
66
+ past_event = double(:event, current?: false)
67
+ current_event = double(:event, current?: true)
68
+ events = [past_event, current_event]
69
+ expect(Event).to receive(:search).and_return(events)
70
+
71
+ subject = expect(Event::Search.new(current: "true").grouped_events).to eq({
72
+ past: [],
73
+ current: [current_event],
74
+ })
75
+ end
76
+
77
+ it "orders past events by date desc if passed date to the order option" do
78
+ current_event = double(:event, current?: true)
79
+ past_event = double(:event, current?: false)
80
+ other_past_event = double(:event, current?: false)
81
+ expect(Event).to receive(:search).and_return([current_event, past_event, other_past_event])
82
+ expect(Event::Search.new(order: "date").grouped_events).to eq({
83
+ current: [current_event],
84
+ past: [past_event, other_past_event],
85
+ })
86
+ end
87
+ end
88
+
89
+ describe "hard failures" do
90
+ it "should hard fail if given no search query" do
91
+ expect(subject.failure_message).to eq("You must enter a search query")
92
+ expect(subject).to be_hard_failure
93
+ end
94
+
95
+ it "should hard fail if searching by both query and tag" do
96
+ subject = Event::Search.new query: "omg", tag: "bbq"
97
+ expect(subject.failure_message).to eq("You can't search by tag and query at the same time")
98
+ expect(subject).to be_hard_failure
99
+ end
100
+ end
101
+ end
102
+
103
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+
3
+ module Calagator
4
+
5
+ describe Event, :type => :model do
6
+ shared_examples_for "#search" do
7
+ it "returns everything when searching by empty string" do
8
+ event1 = FactoryGirl.create(:event)
9
+ event2 = FactoryGirl.create(:event)
10
+ expect(Event.search("")).to match_array([event1, event2])
11
+ end
12
+
13
+ it "searches event titles by substring" do
14
+ event1 = FactoryGirl.create(:event, title: "wtfbbq")
15
+ event2 = FactoryGirl.create(:event, title: "zomg!")
16
+ expect(Event.search("zomg")).to eq([event2])
17
+ end
18
+
19
+ it "searches event descriptions by substring" do
20
+ event1 = FactoryGirl.create(:event, description: "wtfbbq")
21
+ event2 = FactoryGirl.create(:event, description: "zomg!")
22
+ expect(Event.search("zomg")).to eq([event2])
23
+ end
24
+
25
+ it "searches event tags by exact match" do
26
+ event1 = FactoryGirl.create(:event, tag_list: ["wtf", "bbq", "zomg"])
27
+ event2 = FactoryGirl.create(:event, tag_list: ["wtf", "bbq", "omg"])
28
+ expect(Event.search("omg")).to eq([event2])
29
+ end
30
+
31
+ it "does not search multiple terms" do
32
+ event1 = FactoryGirl.create(:event, title: "wtf")
33
+ event2 = FactoryGirl.create(:event, title: "zomg!")
34
+ event3 = FactoryGirl.create(:event, title: "bbq")
35
+ expect(Event.search("wtf zomg")).to match_array([])
36
+ end
37
+
38
+ it "searches case-insensitively" do
39
+ event1 = FactoryGirl.create(:event, title: "WTFBBQ")
40
+ event2 = FactoryGirl.create(:event, title: "ZOMG!")
41
+ expect(Event.search("zomg")).to eq([event2])
42
+ end
43
+
44
+ it "sorts by start time descending" do
45
+ event2 = FactoryGirl.create(:event, start_time: 1.day.ago)
46
+ event1 = FactoryGirl.create(:event, start_time: 1.day.from_now)
47
+ expect(Event.search("")).to eq([event1, event2])
48
+ end
49
+
50
+ it "can sort by event title" do
51
+ event2 = FactoryGirl.create(:event, title: "zomg")
52
+ event1 = FactoryGirl.create(:event, title: "omg")
53
+ expect(Event.search("", order: "name")).to eq([event1, event2])
54
+ end
55
+
56
+ it "can sort by venue title" do
57
+ event2 = FactoryGirl.create(:event, venue: FactoryGirl.create(:venue, title: "zomg"))
58
+ event1 = FactoryGirl.create(:event, venue: FactoryGirl.create(:venue, title: "omg"))
59
+ expect(Event.search("", order: "venue")).to eq([event1, event2])
60
+ end
61
+
62
+ it "can sort by start date" do
63
+ event2 = FactoryGirl.create(:event, start_time: 1.year.ago)
64
+ event1 = FactoryGirl.create(:event, start_time: 1.year.from_now)
65
+ expect(Event.search("", order: "date")).to eq([event1, event2])
66
+ end
67
+
68
+ it "can limit to current and upcoming events" do
69
+ event1 = FactoryGirl.create(:event, start_time: 1.year.ago, end_time: 1.year.ago + 1.hour)
70
+ event2 = FactoryGirl.create(:event, start_time: 1.hour.ago, end_time: 1.hour.from_now)
71
+ event3 = FactoryGirl.create(:event, start_time: 1.year.from_now, end_time: 1.year.from_now + 1.hour)
72
+ expect(Event.search("", skip_old: true)).to eq([event3, event2])
73
+ end
74
+
75
+ it "can limit number of events" do
76
+ 2.times { FactoryGirl.create(:event) }
77
+ expect(Event.search("", limit: 1).count).to eq(1)
78
+ end
79
+
80
+ it "limit applies to current and past queries separately" do
81
+ event1 = FactoryGirl.create(:event, title: "omg", start_time: 1.year.ago)
82
+ event2 = FactoryGirl.create(:event, title: "omg", start_time: 1.year.ago)
83
+ event3 = FactoryGirl.create(:event, title: "omg", start_time: 1.year.from_now)
84
+ event4 = FactoryGirl.create(:event, title: "omg", start_time: 1.year.from_now)
85
+ expect(Event.search("omg", limit: 1).to_a.count).to eq(2)
86
+ end
87
+
88
+ it "ANDs terms together to narrow search results" do
89
+ event1 = FactoryGirl.create(:event, title: "women who hack")
90
+ event2 = FactoryGirl.create(:event, title: "women who bike")
91
+ event3 = FactoryGirl.create(:event, title: "omg")
92
+ expect(Event.search("women who hack")).to eq([event1])
93
+ end
94
+ end
95
+
96
+ describe "Sql" do
97
+ # spec_helper defaults all tests to sql
98
+
99
+ it_should_behave_like "#search"
100
+
101
+ it "searches event urls by substring" do
102
+ event1 = FactoryGirl.create(:event, url: "http://example.com/wtfbbq.html")
103
+ event2 = FactoryGirl.create(:event, url: "http://example.com/zomg.html")
104
+ expect(Event.search("zomg")).to eq([event2])
105
+ end
106
+
107
+ it "is using the sql search engine" do
108
+ expect(Event::SearchEngine.kind).to eq(:sql)
109
+ end
110
+
111
+ it "does not provide a score" do
112
+ expect(Event::SearchEngine.score?).to be_falsey
113
+ end
114
+ end
115
+
116
+ describe "Sunspot" do
117
+ around do |example|
118
+ server_running = begin
119
+ # Try opening the configured port. If it works, it's running.
120
+ TCPSocket.new('127.0.0.1', Sunspot::Rails.configuration.port).close
121
+ true
122
+ rescue Errno::ECONNREFUSED
123
+ false
124
+ end
125
+
126
+ if server_running
127
+ Event::SearchEngine.use(:sunspot)
128
+ Venue::SearchEngine.use(:sunspot)
129
+ Event.reindex
130
+ Venue.reindex
131
+ example.run
132
+ else
133
+ pending "Solr not running. Start with `rake sunspot:solr:start RAILS_ENV=test`"
134
+ end
135
+ end
136
+
137
+ it_should_behave_like "#search"
138
+
139
+ it "is using the sunspot search engine" do
140
+ expect(Event::SearchEngine.kind).to eq(:sunspot)
141
+ end
142
+
143
+ it "provides a score" do
144
+ expect(Event::SearchEngine.score?).to be_truthy
145
+ end
146
+ end
147
+ end
148
+
149
+ end
@@ -0,0 +1,859 @@
1
+ require 'spec_helper'
2
+
3
+ module Calagator
4
+
5
+ describe Event, :type => :model do
6
+ describe "in general" do
7
+ it "should be valid" do
8
+ event = Event.new(:title => "Event title", :start_time => Time.zone.parse('2008.04.12'))
9
+ expect(event).to be_valid
10
+ end
11
+
12
+ it "should add a http:// prefix to urls without one" do
13
+ event = Event.new(:title => "Event title", :start_time => Time.zone.parse('2008.04.12'), :url => 'google.com')
14
+ expect(event).to be_valid
15
+ end
16
+
17
+ it "validates blacklisted words" do
18
+ BlacklistValidator.any_instance.stub(patterns: [/\bcialis\b/, /\bviagra\b/])
19
+ event = Event.new(:title => "Foo bar cialis", :start_time => Time.zone.parse('2008.04.12'), :url => 'google.com')
20
+ expect(event).not_to be_valid
21
+ end
22
+
23
+ it "can be locked" do
24
+ event = Event.create(:title => "Event title", :start_time => Time.zone.parse('2008.04.12'))
25
+ event.lock_editing!
26
+ expect(event.locked).to eq(true)
27
+ end
28
+
29
+ it "can be unlocked" do
30
+ event = Event.create(:title => "Event title", :start_time => Time.zone.parse('2008.04.12'), :locked => true)
31
+ event.unlock_editing!
32
+ expect(event.locked).to eq(false)
33
+ end
34
+
35
+ it "can't be deleted if it's locked" do
36
+ event = Event.create(:title => "Event title", :start_time => Time.zone.parse('2008.04.12'))
37
+ event.lock_editing!
38
+ expect(event.destroy).to eq(false)
39
+ end
40
+ end
41
+
42
+ describe "when checking time status" do
43
+ it "should be old if event ended before today" do
44
+ expect(FactoryGirl.build(:event, start_time: 2.days.ago, end_time: 1.day.ago)).to be_old
45
+ end
46
+
47
+ it "should be current if event is happening today" do
48
+ expect(FactoryGirl.build(:event, start_time: 1.hour.from_now)).to be_current
49
+ end
50
+
51
+ it "should be ongoing if it began before today but ends today or later" do
52
+ expect(FactoryGirl.build(:event, start_time: 1.day.ago, end_time: 1.day.from_now)).to be_ongoing
53
+ end
54
+ end
55
+
56
+ describe "dealing with tags" do
57
+ before do
58
+ @tags = "some, tags"
59
+ @event = Event.new(:title => "Tagging Day", :start_time => now)
60
+ end
61
+
62
+ it "should be taggable" do
63
+ expect(@event.tag_list).to eq []
64
+ end
65
+
66
+ it "should just cache tagging if it is a new record" do
67
+ expect(@event).not_to receive :save
68
+ expect(@event).to be_new_record
69
+ @event.tag_list = @tags
70
+ expect(@event.tag_list.to_s).to eq @tags
71
+ end
72
+
73
+ it "should use tags with punctuation" do
74
+ tags = [".net", "foo-bar"]
75
+ @event.tag_list = tags.join(", ")
76
+ @event.save
77
+
78
+ @event.reload
79
+ expect(@event.tags.map(&:name).sort).to eq tags.sort
80
+ end
81
+
82
+ it "should not interpret numeric tags as IDs" do
83
+ tag = "123"
84
+ @event.tag_list = tag
85
+ @event.save
86
+
87
+ @event.reload
88
+ expect(@event.tags.first.name).to eq "123"
89
+ end
90
+
91
+ it "should return a collection of events for a given tag" do
92
+ @event.tag_list = @tags
93
+ @event.save
94
+ expect(Event.tagged_with('tags')).to eq [@event]
95
+ end
96
+ end
97
+
98
+ describe "when parsing" do
99
+
100
+ before do
101
+ @basic_hcal = read_sample('hcal_basic.xml')
102
+ @basic_venue = mock_model(Venue, :title => 'Argent Hotel, San Francisco, CA', :full_address => '50 3rd St, San Francisco, CA 94103')
103
+ @basic_event = Event.new(
104
+ :title => 'Web 2.0 Conference',
105
+ :url => 'http://www.web2con.com/',
106
+ :start_time => Time.zone.parse('2007-10-05'),
107
+ :end_time => nil,
108
+ :venue => @basic_venue)
109
+ end
110
+
111
+ it "should parse an iCalendar into an Event" do
112
+ url = "http://foo.bar/"
113
+ actual_ical = Event::IcalRenderer.render(@basic_event)
114
+ stub_request(:get, url).to_return(body: actual_ical)
115
+
116
+ events = Source::Parser.to_events(url: url, skip_old: false)
117
+
118
+ expect(events.size).to eq 1
119
+ event = events.first
120
+ expect(event.title).to eq @basic_event.title
121
+ expect(event.url).to eq @basic_event.url
122
+ expect(event.description).to be_blank
123
+
124
+ expect(event.venue.title).to match "#{@basic_event.venue.title}: #{@basic_event.venue.full_address}"
125
+ end
126
+
127
+ it "should parse an iCalendar into an Event without a URL and generate it" do
128
+ generated_url = "http://foo.bar/"
129
+ @basic_event.url = nil
130
+ actual_ical = Event::IcalRenderer.render(@basic_event, :url_helper => lambda{|event| generated_url})
131
+ url = "http://foo.bar/"
132
+ stub_request(:get, url).to_return(body: actual_ical)
133
+
134
+ events = Source::Parser.to_events(url: url, skip_old: false)
135
+
136
+ expect(events.size).to eq 1
137
+ event = events.first
138
+ expect(event.title).to eq @basic_event.title
139
+ expect(event.url).to eq @basic_event.url
140
+ expect(event.description).to match /Imported from: #{generated_url}/
141
+
142
+ expect(event.venue.title).to match "#{@basic_event.venue.title}: #{@basic_event.venue.full_address}"
143
+ end
144
+ end
145
+
146
+ describe "when finding duplicates by type" do
147
+ def assert_default_find_duplicates_by_type(type)
148
+ expect(Event).to receive(:future).and_return 42
149
+ expect(Event.find_duplicates_by_type(type)).to eq({ [] => 42 })
150
+ end
151
+
152
+ it "should find all future events if called with nil" do
153
+ assert_default_find_duplicates_by_type(nil)
154
+ end
155
+
156
+ it "should find all future events if called with empty string" do
157
+ assert_default_find_duplicates_by_type('')
158
+ end
159
+
160
+ it "should find all future events if called with 'na'" do
161
+ assert_default_find_duplicates_by_type('na')
162
+ end
163
+
164
+ def assert_specific_find_by_duplicates_by(type, queried)
165
+ expect(Event).to receive(:find_duplicates_by).with(queried, {:grouped => true, :where => anything()})
166
+ Event.find_duplicates_by_type(type)
167
+ end
168
+
169
+ it "should find events with all duplicate fields if called with 'all'" do
170
+ assert_specific_find_by_duplicates_by('all', :all)
171
+ end
172
+
173
+ it "should find events with any duplicate fields if called with 'any'" do
174
+ assert_specific_find_by_duplicates_by('any', :any)
175
+ end
176
+
177
+ it "should find events with duplicate titles if called with 'title'" do
178
+ assert_specific_find_by_duplicates_by('title', [:title])
179
+ end
180
+ end
181
+
182
+ describe "when processing date" do
183
+ before do
184
+ @event = Event.new(:title => "MyEvent")
185
+ end
186
+
187
+ it "should fail to validate if start time is nil" do
188
+ @event.start_time = nil
189
+ expect(@event).not_to be_valid
190
+ expect(@event.errors[:start_time].size).to eq(1)
191
+ end
192
+
193
+ it "should fail to validate if start time is blank" do
194
+ @event.start_time = ""
195
+ expect(@event).not_to be_valid
196
+ expect(@event.errors[:start_time].size).to eq(1)
197
+ end
198
+
199
+ it "should fail to validate if end_time is earlier than start time " do
200
+ @event.start_time = now
201
+ @event.end_time = @event.start_time - 2.hours
202
+ expect(@event).to be_invalid
203
+ expect(@event.errors[:end_time].size).to eq(1)
204
+ end
205
+ end
206
+
207
+ describe "when processing url" do
208
+ before do
209
+ @event = Event.new(:title => 'MyEvent', :start_time => now)
210
+ end
211
+
212
+ let(:valid_urls) {[
213
+ "hackoregon.org",
214
+ "http://www.meetup.com/Hack_Oregon-Data/events/",
215
+ "example.com",
216
+ "sub.example.com/",
217
+ "sub.domain.my-example.com",
218
+ "example.com/?stuff=true",
219
+ "example.com:5000/?stuff=true",
220
+ "sub.domain.my-example.com/path/to/file/hello.html",
221
+ "hello.museum",
222
+ "http://example.com",
223
+ ]}
224
+
225
+ let(:invalid_urls){[
226
+ "hackoregon.org, http://www.meetup.com/Hack_Oregon-Data/events/",
227
+ "hackoregon.org\nhttp://www.meetup.com/",
228
+ "htttp://www.example.com"
229
+ ]}
230
+
231
+ it "should validate with valid urls (with scheme included or not)" do
232
+ valid_urls.each do |valid_url|
233
+ @event.url = valid_url
234
+ expect(@event).to be_valid
235
+ end
236
+ end
237
+
238
+ it "should fail to validate with invalid urls (with scheme included or not)" do
239
+ invalid_urls.each do |invalid_url|
240
+ @event.url = invalid_url
241
+ expect(@event).to be_invalid
242
+ end
243
+ end
244
+ end
245
+
246
+ describe "#start_time=" do
247
+ it "should clear with nil" do
248
+ expect(Event.new(:start_time => nil).start_time).to be_nil
249
+ end
250
+
251
+ it "should set from date String" do
252
+ event = Event.new(:start_time => "2009-01-02")
253
+ expect(event.start_time).to eq Time.zone.parse("2009-01-02")
254
+ end
255
+
256
+ it "should set from date-time String" do
257
+ event = Event.new(:start_time => "2009-01-02 03:45")
258
+ expect(event.start_time).to eq Time.zone.parse("2009-01-02 03:45")
259
+ end
260
+
261
+ it "should set from an Array of Strings" do
262
+ event = Event.new(:start_time => ["2009-01-03", "02:14"])
263
+ expect(event.start_time).to eq Time.zone.parse("2009-01-03 02:14")
264
+ end
265
+
266
+ it "should set from Date" do
267
+ event = Event.new(:start_time => Date.parse("2009-02-01"))
268
+ expect(event.start_time).to eq Time.parse("2009-02-01")
269
+ end
270
+
271
+ it "should set from DateTime" do
272
+ event = Event.new(:start_time => Time.zone.parse("2009-01-01 05:30"))
273
+ expect(event.start_time).to eq Time.zone.parse("2009-01-01 05:30")
274
+ end
275
+
276
+ it "should flag an invalid time and reset to nil" do
277
+ event = Event.new(:start_time => "2010/1/1")
278
+ event.start_time = "1/0"
279
+ expect(event.start_time).to be_nil
280
+ expect(event.errors[:start_time]).to be_present
281
+ end
282
+ end
283
+
284
+ describe "#end_time=" do
285
+ it "should clear with nil" do
286
+ expect(Event.new(:end_time => nil).end_time).to be_nil
287
+ end
288
+
289
+ it "should set from date String" do
290
+ event = Event.new(:end_time => "2009-01-02")
291
+ expect(event.end_time).to eq Time.zone.parse("2009-01-02")
292
+ end
293
+
294
+ it "should set from date-time String" do
295
+ event = Event.new(:end_time => "2009-01-02 03:45")
296
+ expect(event.end_time).to eq Time.zone.parse("2009-01-02 03:45")
297
+ end
298
+
299
+ it "should set from an Array of Strings" do
300
+ event = Event.new(:end_time => ["2009-01-03", "02:14"])
301
+ expect(event.end_time).to eq Time.zone.parse("2009-01-03 02:14")
302
+ end
303
+
304
+ it "should set from Date" do
305
+ event = Event.new(:end_time => Date.parse("2009-02-01"))
306
+ expect(event.end_time).to eq Time.parse("2009-02-01")
307
+ end
308
+
309
+ it "should set from DateTime" do
310
+ event = Event.new(:end_time => Time.zone.parse("2009-01-01 05:30"))
311
+ expect(event.end_time).to eq Time.zone.parse("2009-01-01 05:30")
312
+ end
313
+
314
+ it "should flag an invalid time" do
315
+ event = Event.new(:end_time => "1/0")
316
+ expect(event.errors[:end_time]).to be_present
317
+ end
318
+ end
319
+
320
+ describe "#dates" do
321
+ it "returns an array of dates spanned by the event" do
322
+ event = Event.new(start_time: "2010-01-01", end_time: "2010-01-03")
323
+ expect(event.dates).to eq([
324
+ Date.parse("2010-01-01"),
325
+ Date.parse("2010-01-02"),
326
+ Date.parse("2010-01-03"),
327
+ ])
328
+ end
329
+
330
+ it "returns an array of one date when there is no end time" do
331
+ event = Event.new(start_time: "2010-01-01")
332
+ expect(event.dates).to eq([Date.parse("2010-01-01")])
333
+ end
334
+
335
+ it "throws ArgumentError when there is no start time" do
336
+ expect { Event.new.dates }.to raise_error(ArgumentError)
337
+ end
338
+ end
339
+
340
+ describe "#duration" do
341
+ it "returns the event length in seconds" do
342
+ event = Event.new(start_time: "2010-01-01", end_time: "2010-01-03")
343
+ expect(event.duration).to eq(172800)
344
+ end
345
+
346
+ it "returns zero if start and end times aren't present" do
347
+ expect(Event.new.duration).to eq(0)
348
+ end
349
+ end
350
+
351
+ describe "#location" do
352
+ it "delegates to the venue's location" do
353
+ event = Event.new
354
+ event.build_venue latitude: 45.5200, longitude: 122.6819
355
+ expect(event.location).to eq([45.5200, 122.6819])
356
+ end
357
+ end
358
+
359
+ describe ".search_tag" do
360
+ before do
361
+ @c = FactoryGirl.create(:event, title: "c", tag_list: ["tag", "wtf"], start_time: 3.minutes.ago)
362
+ @b = FactoryGirl.create(:event, title: "b", tag_list: ["omg", "wtf"], start_time: 2.minutes.ago)
363
+ @a = FactoryGirl.create(:event, title: "a", tag_list: ["tag", "omg"], start_time: 1.minutes.ago)
364
+ end
365
+
366
+ it "finds events with the given tag" do
367
+ Event.search_tag("tag").should == [@c,@a]
368
+ end
369
+
370
+ it "accepts an order option" do
371
+ Event.search_tag("tag", order: "name").should == [@a,@c]
372
+ end
373
+ end
374
+
375
+ describe "when finding by dates" do
376
+
377
+ before do
378
+ @today_midnight = today
379
+ @yesterday = @today_midnight.yesterday
380
+ @tomorrow = @today_midnight.tomorrow
381
+
382
+ @this_venue = Venue.create!(:title => "This venue")
383
+
384
+ @started_before_today_and_ends_after_today = Event.create!(
385
+ :title => "Event in progress",
386
+ :start_time => @yesterday,
387
+ :end_time => @tomorrow,
388
+ :venue_id => @this_venue.id)
389
+
390
+ @started_midnight_and_continuing_after = Event.create!(
391
+ :title => "Midnight start",
392
+ :start_time => @today_midnight,
393
+ :end_time => @tomorrow,
394
+ :venue_id => @this_venue.id)
395
+
396
+ @started_and_ended_yesterday = Event.create!(
397
+ :title => "Yesterday start",
398
+ :start_time => @yesterday,
399
+ :end_time => @yesterday.end_of_day,
400
+ :venue_id => @this_venue.id)
401
+
402
+ @started_today_and_no_end_time = Event.create!(
403
+ :title => "nil end time",
404
+ :start_time => @today_midnight,
405
+ :end_time => nil,
406
+ :venue_id => @this_venue.id)
407
+
408
+ @starts_and_ends_tomorrow = Event.create!(
409
+ :title => "starts and ends tomorrow",
410
+ :start_time => @tomorrow,
411
+ :end_time => @tomorrow.end_of_day,
412
+ :venue_id => @this_venue.id)
413
+
414
+ @starts_after_tomorrow = Event.create!(
415
+ :title => "Starting after tomorrow",
416
+ :start_time => @tomorrow + 1.day,
417
+ :venue_id => @this_venue.id)
418
+
419
+ @started_before_today_and_ends_at_midnight = Event.create!(
420
+ :title => "Midnight end",
421
+ :start_time => @yesterday,
422
+ :end_time => @today_midnight,
423
+ :venue_id => @this_venue.id)
424
+
425
+ @future_events_for_this_venue = @this_venue.events.future
426
+ end
427
+
428
+ describe "for future events" do
429
+ before do
430
+ @future_events = Event.future
431
+ end
432
+
433
+ it "should include events that started earlier today" do
434
+ expect(@future_events).to include @started_midnight_and_continuing_after
435
+ end
436
+
437
+ it "should include events with no end time that started today" do
438
+ expect(@future_events).to include @started_today_and_no_end_time
439
+ end
440
+
441
+ it "should include events that started before today and ended after today" do
442
+ events = Event.future
443
+ expect(events).to include @started_before_today_and_ends_after_today
444
+ end
445
+
446
+ it "should include events with no end time that started today" do
447
+ expect(@future_events).to include @started_today_and_no_end_time
448
+ end
449
+
450
+ it "should not include events that ended before today" do
451
+ expect(@future_events).not_to include @started_and_ended_yesterday
452
+ end
453
+ end
454
+
455
+ describe "for future events with venue" do
456
+ before do
457
+ @another_venue = Venue.create!(:title => "Another venue")
458
+
459
+ @future_event_another_venue = Event.create!(
460
+ :title => "Starting after tomorrow",
461
+ :start_time => @tomorrow + 1.day,
462
+ :venue_id => @another_venue.id)
463
+
464
+ @future_event_no_venue = Event.create!(
465
+ :title => "Starting after tomorrow",
466
+ :start_time => @tomorrow + 1.day)
467
+ end
468
+
469
+ # TODO Consider moving these examples elsewhere because they don't appear to relate to this scope. This comment applies to the examples from here...
470
+ it "should include events that started earlier today" do
471
+ expect(@future_events_for_this_venue).to include @started_midnight_and_continuing_after
472
+ end
473
+
474
+ it "should include events with no end time that started today" do
475
+ expect(@future_events_for_this_venue).to include @started_today_and_no_end_time
476
+ end
477
+
478
+ it "should include events that started before today and ended after today" do
479
+ expect(@future_events_for_this_venue).to include @started_before_today_and_ends_after_today
480
+ end
481
+
482
+ it "should not include events that ended before today" do
483
+ expect(@future_events_for_this_venue).not_to include @started_and_ended_yesterday
484
+ end
485
+ # TODO ...to here.
486
+
487
+ it "should not include events for another venue" do
488
+ expect(@future_events_for_this_venue).not_to include @future_event_another_venue
489
+ end
490
+
491
+ it "should not include events with no venue" do
492
+ expect(@future_events_for_this_venue).not_to include @future_event_no_venue
493
+ end
494
+ end
495
+
496
+ describe "for date range" do
497
+ it "should include events that started earlier today" do
498
+ events = Event.within_dates(@today_midnight, @tomorrow)
499
+ expect(events).to include @started_midnight_and_continuing_after
500
+ end
501
+
502
+ it "should include events that started before today and end after today" do
503
+ events = Event.within_dates(@today_midnight, @tomorrow)
504
+ expect(events).to include @started_before_today_and_ends_after_today
505
+ end
506
+
507
+ it "should not include past events" do
508
+ events = Event.within_dates(@today_midnight, @tomorrow)
509
+ expect(events).not_to include @started_and_ended_yesterday
510
+ end
511
+
512
+ it "should exclude events that start after the end of the range" do
513
+ events = Event.within_dates(@tomorrow, @tomorrow)
514
+ expect(events).not_to include @started_today_and_no_end_time
515
+ end
516
+ end
517
+ end
518
+
519
+ describe "when ordering" do
520
+ describe "with .ordered_by_ui_field" do
521
+ it "defaults to order by start time" do
522
+ event1 = FactoryGirl.create(:event, start_time: Time.zone.parse("2003-01-01"))
523
+ event2 = FactoryGirl.create(:event, start_time: Time.zone.parse("2002-01-01"))
524
+ event3 = FactoryGirl.create(:event, start_time: Time.zone.parse("2001-01-01"))
525
+
526
+ events = Event.ordered_by_ui_field(nil)
527
+ expect(events).to eq([event3, event2, event1])
528
+ end
529
+
530
+ it "can order by event name" do
531
+ event1 = FactoryGirl.create(:event, title: "CU there")
532
+ event2 = FactoryGirl.create(:event, title: "Be there")
533
+ event3 = FactoryGirl.create(:event, title: "An event")
534
+
535
+ events = Event.ordered_by_ui_field("name")
536
+ expect(events).to eq([event3, event2, event1])
537
+ end
538
+
539
+ it "can order by venue name" do
540
+ event1 = FactoryGirl.create(:event, venue: FactoryGirl.create(:venue, title: "C venue"))
541
+ event2 = FactoryGirl.create(:event, venue: FactoryGirl.create(:venue, title: "B venue"))
542
+ event3 = FactoryGirl.create(:event, venue: FactoryGirl.create(:venue, title: "A venue"))
543
+
544
+ events = Event.ordered_by_ui_field("venue")
545
+ expect(events).to eq([event3, event2, event1])
546
+ end
547
+ end
548
+ end
549
+
550
+ describe "with finding duplicates" do
551
+ before do
552
+ @non_duplicate_event = FactoryGirl.create(:event)
553
+ @duplicate_event = FactoryGirl.create(:duplicate_event)
554
+ @events = [@non_duplicate_event, @duplicate_event]
555
+ end
556
+
557
+ it "should find all events that have not been marked as duplicate" do
558
+ non_duplicates = Event.non_duplicates
559
+ expect(non_duplicates).to include @non_duplicate_event
560
+ expect(non_duplicates).not_to include @duplicate_event
561
+ end
562
+
563
+ it "should find all events that have been marked as duplicate" do
564
+ duplicates = Event.marked_duplicates
565
+ expect(duplicates).to include @duplicate_event
566
+ expect(duplicates).not_to include @non_duplicate_event
567
+ end
568
+ end
569
+
570
+ describe "with finding duplicates (integration test)" do
571
+ before do
572
+ @event = FactoryGirl.create(:event)
573
+ end
574
+
575
+ # Find duplicates, create another event with the given attributes, and find duplicates again
576
+ # TODO Refactor #find_duplicates_create_a_clone_and_find_again and its uses into something simpler, like #assert_duplicate_count.
577
+ def find_duplicates_create_a_clone_and_find_again(find_duplicates_arguments, clone_attributes, create_class = Event)
578
+ before_results = create_class.find_duplicates_by( find_duplicates_arguments)
579
+ clone = create_class.create!(clone_attributes)
580
+ after_results = Event.find_duplicates_by(find_duplicates_arguments)
581
+ return [before_results.sort_by(&:created_at), after_results.sort_by(&:created_at)]
582
+ end
583
+
584
+ it "should find duplicate title by title" do
585
+ pre, post = find_duplicates_create_a_clone_and_find_again(:title, {:title => @event.title, :start_time => @event.start_time} )
586
+ expect(post.size).to eq(pre.size + 2)
587
+ end
588
+
589
+ it "should find duplicate title by any" do
590
+ # TODO figure out why the #find_duplicates_create_a_clone_and_find_again isn't giving expected results and a workaround was needed.
591
+ #pre, post = find_duplicates_create_a_clone_and_find_again(:any, {:title => @event.title, :start_time => @event.start_time} )
592
+ #post.size.should eq(pre.size + 2)
593
+ dup_title = Event.create!({:title => @event.title, :start_time => @event.start_time + 1.minute})
594
+ expect(Event.find_duplicates_by(:any)).to include dup_title
595
+ end
596
+
597
+ it "should not find duplicate title by url" do
598
+ pre, post = find_duplicates_create_a_clone_and_find_again(:url, {:title => @event.title, :start_time => @event.start_time} )
599
+ expect(post.size).to eq pre.size
600
+ end
601
+
602
+ it "should find complete duplicates by all" do
603
+ pre, post = find_duplicates_create_a_clone_and_find_again(:all, @event.attributes)
604
+ expect(post.size).to eq(pre.size + 2)
605
+ end
606
+
607
+ it "should not find incomplete duplicates by all" do
608
+ pre, post = find_duplicates_create_a_clone_and_find_again(:all, @event.attributes.merge(:title => "SpaceCube", :start_time => @event.start_time ))
609
+ expect(post.size).to eq pre.size
610
+ end
611
+
612
+ it "should find duplicate for matching multiple fields" do
613
+ pre, post = find_duplicates_create_a_clone_and_find_again([:title, :start_time], {:title => @event.title, :start_time => @event.start_time })
614
+ expect(post.size).to eq(pre.size + 2)
615
+ end
616
+
617
+ it "should not find duplicates for mismatching multiple fields" do
618
+ pre, post = find_duplicates_create_a_clone_and_find_again([:title, :start_time], {:title => "SpaceCube", :start_time => @event.start_time })
619
+ expect(post.size).to eq pre.size
620
+ end
621
+ end
622
+
623
+ describe "when squashing duplicates (integration test)" do
624
+ before do
625
+ @event = FactoryGirl.create(:event)
626
+ end
627
+
628
+ it "should consolidate associations, and merge tags" do
629
+ @event.tag_list = %w[first second] # master event contains one duplicate tag, and one unique tag
630
+
631
+ clone = Event.create!(@event.attributes)
632
+ clone.tag_list.replace %w[second third] # duplicate event also contains one duplicate tag, and one unique tag
633
+ clone.save!
634
+ clone.reload
635
+ expect(clone).not_to be_duplicate
636
+
637
+ Event.squash(@event, clone)
638
+ expect(@event.tag_list.to_a.sort).to eq %w[first second third] # master now contains all three tags
639
+ expect(clone.duplicate_of).to eq @event
640
+ end
641
+ end
642
+
643
+ describe "when checking for squashing" do
644
+ before do
645
+ @today = today
646
+ @master = Event.create!(:title => "Master", :start_time => @today)
647
+ @slave1 = Event.create!(:title => "1st slave", :start_time => @today, :duplicate_of_id => @master.id)
648
+ @slave2 = Event.create!(:title => "2nd slave", :start_time => @today, :duplicate_of_id => @slave1.id)
649
+ @orphan = Event.create!(:title => "orphan", :start_time => @today, :duplicate_of_id => 999999)
650
+ end
651
+
652
+ it "should recognize a master" do
653
+ expect(@master).to be_a_master
654
+ end
655
+
656
+ it "should recognize a slave" do
657
+ expect(@slave1).to be_a_slave
658
+ end
659
+
660
+ it "should not think that a slave is a master" do
661
+ expect(@slave2).not_to be_a_master
662
+ end
663
+
664
+ it "should not think that a master is a slave" do
665
+ expect(@master).not_to be_a_slave
666
+ end
667
+
668
+ it "should return the progenitor of a child" do
669
+ expect(@slave1.progenitor).to eq @master
670
+ end
671
+
672
+ it "should return the progenitor of a grandchild" do
673
+ expect(@slave2.progenitor).to eq @master
674
+ end
675
+
676
+ it "should return a master as its own progenitor" do
677
+ expect(@master.progenitor).to eq @master
678
+ end
679
+
680
+ it "should return a marked duplicate as progenitor if it is orphaned" do
681
+ expect(@orphan.progenitor).to eq @orphan
682
+ end
683
+ end
684
+
685
+ describe "when versioning" do
686
+ it "should have versions" do
687
+ expect(Event.new.versions).to eq []
688
+ end
689
+
690
+ it "should create a new version after updating" do
691
+ event = Event.create!(:title => "Event title", :start_time => Time.zone.parse('2008.04.12'))
692
+ expect(event.versions.count).to eq 1
693
+
694
+ event.title = "New Title"
695
+ event.save!
696
+ expect(event.versions.count).to eq 2
697
+ end
698
+ end
699
+
700
+ describe "when normalizing line-endings in the description" do
701
+ before do
702
+ @event = Event.new
703
+ end
704
+
705
+ it "should not molest contents without carriage-returns" do
706
+ @event.description = "foo\nbar"
707
+ expect(@event.description).to eq "foo\nbar"
708
+ end
709
+
710
+ it "should replace CRLF with LF" do
711
+ @event.description = "foo\r\nbar"
712
+ expect(@event.description).to eq "foo\nbar"
713
+ end
714
+
715
+ it "should replace stand-alone CR with LF" do
716
+ @event.description = "foo\rbar"
717
+ expect(@event.description).to eq "foo\nbar"
718
+ end
719
+ end
720
+
721
+ describe "when converting to iCal" do
722
+ def ical_roundtrip(events, opts = {})
723
+ parsed_events = RiCal.parse_string(Event::IcalRenderer.render(events, opts)).first.events
724
+ if events.is_a?(Event)
725
+ parsed_events.first
726
+ else
727
+ parsed_events
728
+ end
729
+ end
730
+
731
+ it "should produce parsable iCal output" do
732
+ expect { ical_roundtrip( FactoryGirl.build(:event) ) }.not_to raise_error
733
+ end
734
+
735
+ it "should represent an event without an end time as a 1-hour block" do
736
+ event = FactoryGirl.build(:event, :start_time => now, :end_time => nil)
737
+ expect(event.end_time).to be_blank
738
+
739
+ rt = ical_roundtrip(event)
740
+ expect(rt.dtend - rt.dtstart).to eq 1.hour
741
+ end
742
+
743
+ it "should set the appropriate end time if one is given" do
744
+ event = FactoryGirl.build(:event, :start_time => now, :end_time => now + 2.hours)
745
+
746
+ rt = ical_roundtrip(event)
747
+ expect(rt.dtend - rt.dtstart).to eq 2.hours
748
+ end
749
+
750
+ describe "when comparing Event's attributes to its iCalendar output" do
751
+ let(:event) { FactoryGirl.build(:event, :id => 123, :created_at => now) }
752
+ let(:ical) { ical_roundtrip(event) }
753
+
754
+ { :summary => :title,
755
+ :created => :created_at,
756
+ :last_modified => :updated_at,
757
+ :description => :description,
758
+ :url => :url,
759
+ :dtstart => :start_time,
760
+ :dtstamp => :created_at
761
+ }.each do |ical_attribute, model_attribute|
762
+ it "should map the Event's #{model_attribute} attribute to '#{ical_attribute}' in the iCalendar output" do
763
+ model_value = event.send(model_attribute)
764
+ ical_value = ical.send(ical_attribute)
765
+
766
+ case model_value
767
+ when ActiveSupport::TimeWithZone
768
+ # Compare raw time because one is using local time zone, while other is using UTC time.
769
+ expect(model_value.to_i).to eq ical_value.to_i
770
+ else
771
+ expect(model_value).to eq ical_value
772
+ end
773
+ end
774
+ end
775
+ end
776
+
777
+ it "should call the URL helper to generate a UID" do
778
+ event = FactoryGirl.build(:event)
779
+ expect(ical_roundtrip(event, :url_helper => lambda {|e| "UID'D!" }).uid).to eq "UID'D!"
780
+ end
781
+
782
+ it "should strip HTML from the description" do
783
+ event = FactoryGirl.create(:event, :description => "<blink>OMFG HTML IS TEH AWESOME</blink>")
784
+ expect(ical_roundtrip(event).description).not_to include "<blink>"
785
+ end
786
+
787
+ it "should include tags in the description" do
788
+ event = FactoryGirl.build(:event)
789
+ event.tag_list = "tags, folksonomy, categorization"
790
+ expect(ical_roundtrip(event).description).to include event.tag_list.to_s
791
+ end
792
+
793
+ it "should leave URL blank if no URL is provided" do
794
+ event = FactoryGirl.build(:event, :url => nil)
795
+ expect(ical_roundtrip(event).url).to be_nil
796
+ end
797
+
798
+ it "should have Source URL if URL helper is given)" do
799
+ event = FactoryGirl.build(:event)
800
+ expect(ical_roundtrip(event, :url_helper => lambda{|e| "FAKE"} ).description).to match /FAKE/
801
+ end
802
+
803
+ it "should create multi-day entries for multi-day events" do
804
+ time = Time.zone.now
805
+ event = FactoryGirl.build(:event, :start_time => time, :end_time => time + 4.days)
806
+ parsed_event = ical_roundtrip( event )
807
+
808
+ start_time = Date.current
809
+ expect(parsed_event.dtstart).to eq start_time
810
+ expect(parsed_event.dtend).to eq(start_time + 5.days)
811
+ end
812
+
813
+ describe "sequence" do
814
+ def event_to_ical(event)
815
+ return RiCal.parse_string(Event::IcalRenderer.render([event])).first.events.first
816
+ end
817
+
818
+ it "should set an initial sequence on a new event" do
819
+ event = FactoryGirl.create(:event)
820
+ ical = event_to_ical(event)
821
+ expect(ical.sequence).to eq 1
822
+ end
823
+
824
+ it "should increment the sequence if it is updated" do
825
+ event = FactoryGirl.create(:event)
826
+ event.update_attribute(:title, "Update 1")
827
+ ical = event_to_ical(event)
828
+ expect(ical.sequence).to eq 2
829
+ end
830
+
831
+ # it "should offset the squence based the global SECRETS.icalendar_sequence_offset" do
832
+ # SECRETS.should_receive(:icalendar_sequence_offset).and_return(41)
833
+ # event = FactoryGirl.build(:event)
834
+ # ical = event_to_ical(event)
835
+ # ical.sequence.should eq 42
836
+ # end
837
+ end
838
+
839
+ describe "- the headers" do
840
+ before do
841
+ @data = Event::IcalRenderer.render(FactoryGirl.build(:event))
842
+ end
843
+
844
+ it "should include the calendar name" do
845
+ expect(@data).to match /\sX-WR-CALNAME:#{Calagator.title}\s/
846
+ end
847
+
848
+ it "should include the method" do
849
+ expect(@data).to match /\sMETHOD:PUBLISH\s/
850
+ end
851
+
852
+ it "should include the scale" do
853
+ expect(@data).to match /\sCALSCALE:Gregorian\s/i
854
+ end
855
+ end
856
+ end
857
+ end
858
+
859
+ end