surveyor_gui 0.0.3

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 (283) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +44 -0
  3. data/.travis.yml +17 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE +21 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +159 -0
  8. data/Rakefile +66 -0
  9. data/app/controllers/answers_controller.rb +3 -0
  10. data/app/controllers/application_controller.rb +3 -0
  11. data/app/controllers/dependencys_controller.rb +286 -0
  12. data/app/controllers/question_groups_controller.rb +84 -0
  13. data/app/controllers/questions_controller.rb +187 -0
  14. data/app/controllers/survey_sections_controller.rb +80 -0
  15. data/app/controllers/surveyforms_controller.rb +4 -0
  16. data/app/controllers/surveyor_controller.rb +89 -0
  17. data/app/controllers/surveyor_gui/reports_controller.rb +339 -0
  18. data/app/controllers/surveyor_gui/responses_controller.rb +39 -0
  19. data/app/controllers/surveyor_gui/survey_controller.rb +16 -0
  20. data/app/facades/report_formatter.rb +44 -0
  21. data/app/facades/report_preview_wrapper.rb +11 -0
  22. data/app/facades/report_response_generator.rb +147 -0
  23. data/app/helpers/application_helper.rb +2 -0
  24. data/app/helpers/dependency_helper.rb +5 -0
  25. data/app/helpers/question_helper.rb +22 -0
  26. data/app/helpers/surveyform_helper.rb +179 -0
  27. data/app/helpers/surveyor_gui/report_helper.rb +19 -0
  28. data/app/helpers/surveyor_helper.rb +4 -0
  29. data/app/inputs/currency_input.rb +5 -0
  30. data/app/inputs/date_picker_input.rb +7 -0
  31. data/app/inputs/datetime_picker_input.rb +5 -0
  32. data/app/inputs/percent_input.rb +5 -0
  33. data/app/inputs/time_picker_input.rb +5 -0
  34. data/app/mailers/.gitkeep +0 -0
  35. data/app/models/.gitkeep +0 -0
  36. data/app/models/answer.rb +4 -0
  37. data/app/models/column.rb +3 -0
  38. data/app/models/dependency.rb +4 -0
  39. data/app/models/dependency_condition.rb +4 -0
  40. data/app/models/permitted_params_decorator.rb +80 -0
  41. data/app/models/question.rb +4 -0
  42. data/app/models/question_group.rb +4 -0
  43. data/app/models/question_type.rb +7 -0
  44. data/app/models/response.rb +4 -0
  45. data/app/models/response_set.rb +4 -0
  46. data/app/models/row.rb +3 -0
  47. data/app/models/survey.rb +4 -0
  48. data/app/models/survey_section.rb +4 -0
  49. data/app/models/surveyform.rb +103 -0
  50. data/app/uploaders/blob_uploader.rb +48 -0
  51. data/app/views/dependencys/_column.html.erb +1 -0
  52. data/app/views/dependencys/_dependency.html.erb +22 -0
  53. data/app/views/dependencys/_dependency_condition_fields.html.erb +48 -0
  54. data/app/views/dependencys/_form.html.erb +20 -0
  55. data/app/views/dependencys/blank.html.erb +0 -0
  56. data/app/views/dependencys/edit.html.erb +1 -0
  57. data/app/views/dependencys/new.html.erb +1 -0
  58. data/app/views/layouts/application.html.erb +14 -0
  59. data/app/views/layouts/surveyor_gui_blank.html.erb +12 -0
  60. data/app/views/layouts/surveyor_gui_default.html.erb +16 -0
  61. data/app/views/layouts/surveyor_modified.html.erb +14 -0
  62. data/app/views/partials/_answer.html.haml +33 -0
  63. data/app/views/partials/_question.html.haml +33 -0
  64. data/app/views/partials/_question_group.html.haml +73 -0
  65. data/app/views/partials/_section.html.haml +13 -0
  66. data/app/views/question_groups/_form.html.erb +56 -0
  67. data/app/views/question_groups/_group_inline_field.html.erb +21 -0
  68. data/app/views/question_groups/_group_inline_fields.html.erb +25 -0
  69. data/app/views/question_groups/blank.html.erb +0 -0
  70. data/app/views/question_groups/edit.html.erb +1 -0
  71. data/app/views/question_groups/new.html.erb +1 -0
  72. data/app/views/questions/_answer_fields.html.erb +23 -0
  73. data/app/views/questions/_answer_options.html.erb +28 -0
  74. data/app/views/questions/_form.html.erb +65 -0
  75. data/app/views/questions/_grid_dropdown_columns.html.erb +10 -0
  76. data/app/views/questions/_grid_dropdown_fields.html.erb +42 -0
  77. data/app/views/questions/_grid_fields.html.erb +9 -0
  78. data/app/views/questions/_no_picks.html.erb +29 -0
  79. data/app/views/questions/_pick.html +21 -0
  80. data/app/views/questions/_picks.html.erb +12 -0
  81. data/app/views/questions/blank.html.erb +0 -0
  82. data/app/views/questions/edit.html.erb +1 -0
  83. data/app/views/questions/new.html.erb +1 -0
  84. data/app/views/survey_sections/_form.html.erb +13 -0
  85. data/app/views/survey_sections/blank.html.erb +0 -0
  86. data/app/views/survey_sections/edit.html.erb +1 -0
  87. data/app/views/survey_sections/new.html.erb +1 -0
  88. data/app/views/surveyforms/_form.html.erb +50 -0
  89. data/app/views/surveyforms/_question_field.html.erb +148 -0
  90. data/app/views/surveyforms/_question_group.html.erb +116 -0
  91. data/app/views/surveyforms/_question_group_fields.html.erb +3 -0
  92. data/app/views/surveyforms/_question_name_and_number.html.erb +6 -0
  93. data/app/views/surveyforms/_question_section.html.erb +15 -0
  94. data/app/views/surveyforms/_question_wrapper.html.erb +109 -0
  95. data/app/views/surveyforms/_survey_section_fields.html.erb +138 -0
  96. data/app/views/surveyforms/clone_survey.html.erb +13 -0
  97. data/app/views/surveyforms/edit.html.erb +5 -0
  98. data/app/views/surveyforms/index.html.erb +40 -0
  99. data/app/views/surveyforms/new.html.erb +1 -0
  100. data/app/views/surveyforms/show.html.erb +5 -0
  101. data/app/views/surveyor_gui/reports/_graphs.html.haml +21 -0
  102. data/app/views/surveyor_gui/reports/_grid.html.haml +42 -0
  103. data/app/views/surveyor_gui/reports/_grid_dropdown.html.haml +56 -0
  104. data/app/views/surveyor_gui/reports/_repeater.html.haml +28 -0
  105. data/app/views/surveyor_gui/reports/_show_report.html.haml +33 -0
  106. data/app/views/surveyor_gui/reports/_single_question.html.haml +70 -0
  107. data/app/views/surveyor_gui/reports/show.html.erb +14 -0
  108. data/app/views/surveyor_gui/responses/_grid.html.haml +20 -0
  109. data/app/views/surveyor_gui/responses/_grid_dropdown.html.haml +20 -0
  110. data/app/views/surveyor_gui/responses/_repeater.html.haml +22 -0
  111. data/app/views/surveyor_gui/responses/_survey_results.html.haml +40 -0
  112. data/app/views/surveyor_gui/responses/index.html.haml +24 -0
  113. data/app/views/surveyor_gui/responses/show.html.haml +42 -0
  114. data/app/views/surveyor_gui/shared/_grid_comments.html.haml +10 -0
  115. data/app/views/surveyor_gui/shared/_new_line.html.haml +2 -0
  116. data/app/views/surveyor_gui/shared/_pick_comments.html.haml +15 -0
  117. data/app/views/surveyor_gui/shared/_question_number.html.haml +9 -0
  118. data/app/views/surveyor_gui/shared/_report_data.html.haml +24 -0
  119. data/app/views/surveyor_gui/shared/_stars_report_data.html.haml +14 -0
  120. data/config.ru +4 -0
  121. data/config/environment.rb +0 -0
  122. data/config/routes.rb +71 -0
  123. data/db/migrate/20140307204049_add_template_to_surveys.rb +5 -0
  124. data/db/migrate/20140307235607_add_test_data_to_response_sets.rb +5 -0
  125. data/db/migrate/20140308171947_add_original_choice_to_answers.rb +5 -0
  126. data/db/migrate/20140308172118_add_blob_to_responses.rb +5 -0
  127. data/db/migrate/20140308172417_add_modifiable_to_survey_section.rb +5 -0
  128. data/db/migrate/20140308174532_add_modifiable_to_question.rb +5 -0
  129. data/db/migrate/20140308175305_add_dynamically_generate_to_questions.rb +5 -0
  130. data/db/migrate/20140311032923_add_dummy_blob_to_questions.rb +5 -0
  131. data/db/migrate/20140311160609_add_dynamic_source_to_questions.rb +5 -0
  132. data/db/migrate/20140311161714_add_report_code_to_questions.rb +5 -0
  133. data/db/migrate/20140530181134_add_is_comment_to_questions.rb +5 -0
  134. data/db/migrate/20140531012006_add_is_comment_to_answers.rb +5 -0
  135. data/db/migrate/20140531225529_create_rows.rb +9 -0
  136. data/db/migrate/20140601011151_create_columns.rb +11 -0
  137. data/db/migrate/20140602030330_add_column_id_to_answers.rb +5 -0
  138. data/db/migrate/20140603155606_add_column_id_to_responses.rb +5 -0
  139. data/db/migrate/20140606023527_add_column_id_to_dependency_conditions.rb +5 -0
  140. data/db/migrate/20140815165307_add_user_id_to_survey.rb +5 -0
  141. data/lib/assets/.gitkeep +0 -0
  142. data/lib/assets/images/addicon.png +0 -0
  143. data/lib/assets/images/datepicker.gif +0 -0
  144. data/lib/assets/images/delete.gif +0 -0
  145. data/lib/assets/images/delete.png +0 -0
  146. data/lib/assets/images/images/border.png +0 -0
  147. data/lib/assets/images/images/controls.png +0 -0
  148. data/lib/assets/images/images/loading.gif +0 -0
  149. data/lib/assets/images/images/loading_background.png +0 -0
  150. data/lib/assets/images/images/overlay.png +0 -0
  151. data/lib/assets/images/rails.png +0 -0
  152. data/lib/assets/images/star.gif +0 -0
  153. data/lib/assets/javascripts/.gitkeep +0 -0
  154. data/lib/assets/javascripts/surveyor_gui/jquery.MetaData.js +121 -0
  155. data/lib/assets/javascripts/surveyor_gui/jquery.blockUI.js +619 -0
  156. data/lib/assets/javascripts/surveyor_gui/jquery.rating.js +377 -0
  157. data/lib/assets/javascripts/surveyor_gui/jquery.validate.js +1188 -0
  158. data/lib/assets/javascripts/surveyor_gui/surveyor_add_ons.js +10 -0
  159. data/lib/assets/javascripts/surveyor_gui/surveyor_gui.js +1417 -0
  160. data/lib/assets/stylesheets/.gitkeep +0 -0
  161. data/lib/assets/stylesheets/surveyor_gui/jquery.rating.css +12 -0
  162. data/lib/assets/stylesheets/surveyor_gui/surveyor_add_ons.css +3 -0
  163. data/lib/assets/stylesheets/surveyor_gui/surveyor_gui.sass +650 -0
  164. data/lib/enumerable_extenders.rb +31 -0
  165. data/lib/generators/surveyor_gui/install_generator.rb +57 -0
  166. data/lib/generators/surveyor_gui/templates/app/assets/javascripts/surveyor_add_ons.js +2 -0
  167. data/lib/generators/surveyor_gui/templates/app/assets/javascripts/surveyor_gui_all.js +24 -0
  168. data/lib/generators/surveyor_gui/templates/app/assets/stylesheets/surveyor_add_ons.css.sass +1 -0
  169. data/lib/generators/surveyor_gui/templates/app/assets/stylesheets/surveyor_gui_all.css.sass +8 -0
  170. data/lib/generators/surveyor_gui/templates/app/helpers/surveyor_helper.rb +4 -0
  171. data/lib/generators/surveyor_gui/templates/app/models/response_set_user.rb +13 -0
  172. data/lib/generators/surveyor_gui/templates/app/views/layouts/surveyor_gui_default.html.erb +16 -0
  173. data/lib/generators/surveyor_gui/templates/config/initializers/simple_form.rb +140 -0
  174. data/lib/generators/surveyor_gui/templates/config/locales/en.yml +5 -0
  175. data/lib/surveyor_gui.rb +17 -0
  176. data/lib/surveyor_gui/engine.rb +30 -0
  177. data/lib/surveyor_gui/helpers/surveyor_helper_methods.rb +19 -0
  178. data/lib/surveyor_gui/models/answer_methods.rb +24 -0
  179. data/lib/surveyor_gui/models/column_methods.rb +43 -0
  180. data/lib/surveyor_gui/models/dependency_condition_methods.rb +53 -0
  181. data/lib/surveyor_gui/models/dependency_methods.rb +83 -0
  182. data/lib/surveyor_gui/models/question_and_group_shared_methods.rb +11 -0
  183. data/lib/surveyor_gui/models/question_group_methods.rb +55 -0
  184. data/lib/surveyor_gui/models/question_methods.rb +435 -0
  185. data/lib/surveyor_gui/models/question_type_methods.rb +493 -0
  186. data/lib/surveyor_gui/models/response_methods.rb +67 -0
  187. data/lib/surveyor_gui/models/response_set_methods.rb +54 -0
  188. data/lib/surveyor_gui/models/row_methods.rb +11 -0
  189. data/lib/surveyor_gui/models/survey_methods.rb +32 -0
  190. data/lib/surveyor_gui/models/survey_section_methods.rb +32 -0
  191. data/lib/surveyor_gui/surveyforms_controller_methods.rb +258 -0
  192. data/lib/surveyor_gui/version.rb +3 -0
  193. data/lib/tasks/.gitkeep +0 -0
  194. data/lib/tasks/surveyor_gui_tasks.rake +4 -0
  195. data/lib/templates/erb/scaffold/_form.html.erb +13 -0
  196. data/spec/controllers/surveyforms_controller_spec.rb +361 -0
  197. data/spec/controllers/surveyor_controller_spec.rb +303 -0
  198. data/spec/factories.rb +181 -0
  199. data/spec/features/create_survey_spec.rb +418 -0
  200. data/spec/features/dependencies_spec.rb +61 -0
  201. data/spec/features/rearrange_survey_spec.rb +118 -0
  202. data/spec/features/ui_spec.rb +469 -0
  203. data/spec/fixtures/REDCapDemoDatabase_DataDictionary.csv +127 -0
  204. data/spec/fixtures/chinese_survey.rb +14 -0
  205. data/spec/fixtures/everything.rb +215 -0
  206. data/spec/fixtures/favorites-ish.rb +22 -0
  207. data/spec/fixtures/favorites.rb +22 -0
  208. data/spec/fixtures/feelings.rb +38 -0
  209. data/spec/fixtures/lifestyle.rb +55 -0
  210. data/spec/fixtures/numbers.rb +21 -0
  211. data/spec/fixtures/redcap_new_headers.csv +1 -0
  212. data/spec/fixtures/redcap_siblings.csv +1 -0
  213. data/spec/fixtures/redcap_whitespace.csv +1 -0
  214. data/spec/helpers/formtastic_custom_input_spec.rb +15 -0
  215. data/spec/helpers/surveyor_helper_spec.rb +116 -0
  216. data/spec/lib/common_spec.rb +37 -0
  217. data/spec/lib/parser_spec.rb +393 -0
  218. data/spec/lib/rake_kitchen_sink.rb +42 -0
  219. data/spec/lib/redcap_parser_spec.rb +129 -0
  220. data/spec/lib/unparser_spec.rb +126 -0
  221. data/spec/models/answer_spec.rb +144 -0
  222. data/spec/models/dependency_condition_spec.rb +428 -0
  223. data/spec/models/dependency_spec.rb +90 -0
  224. data/spec/models/question_group_spec.rb +66 -0
  225. data/spec/models/question_spec.rb +176 -0
  226. data/spec/models/response_set_spec.rb +452 -0
  227. data/spec/models/response_spec.rb +208 -0
  228. data/spec/models/survey_section_spec.rb +58 -0
  229. data/spec/models/survey_spec.rb +155 -0
  230. data/spec/models/surveyor_gui/question_spec.rb +60 -0
  231. data/spec/models/surveyor_gui/question_type_spec.rb +268 -0
  232. data/spec/models/validation_condition_spec.rb +98 -0
  233. data/spec/models/validation_spec.rb +64 -0
  234. data/spec/spec.opts +4 -0
  235. data/spec/spec_helper.rb +119 -0
  236. data/spec/support/scenario_outline_helper.rb +39 -0
  237. data/spec/support/shared.rb +10 -0
  238. data/spec/support/surveyforms_creation_helpers.rb +312 -0
  239. data/spec/support/surveyforms_rearrangement_helpers.rb +170 -0
  240. data/spec/support/surveyor_api_helpers.rb +15 -0
  241. data/spec/support/surveyor_ui_helpers.rb +108 -0
  242. data/spec/support/wait_for_ajax.rb +11 -0
  243. data/spec/views/questions/edit.html.erb_spec.rb +73 -0
  244. data/spec/views/surveyforms/edit.html.erb_spec.rb +126 -0
  245. data/surveyor_gui.gemspec +52 -0
  246. data/test/dummy/README.rdoc +261 -0
  247. data/test/dummy/Rakefile +7 -0
  248. data/test/dummy/app/assets/javascripts/application.js +15 -0
  249. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  250. data/test/dummy/app/controllers/application_controller.rb +3 -0
  251. data/test/dummy/app/helpers/application_helper.rb +2 -0
  252. data/test/dummy/app/mailers/.gitkeep +0 -0
  253. data/test/dummy/app/models/.gitkeep +0 -0
  254. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  255. data/test/dummy/config.ru +4 -0
  256. data/test/dummy/config/application.rb +59 -0
  257. data/test/dummy/config/boot.rb +10 -0
  258. data/test/dummy/config/database.yml +25 -0
  259. data/test/dummy/config/environment.rb +5 -0
  260. data/test/dummy/config/environments/development.rb +37 -0
  261. data/test/dummy/config/environments/production.rb +67 -0
  262. data/test/dummy/config/environments/test.rb +37 -0
  263. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  264. data/test/dummy/config/initializers/inflections.rb +15 -0
  265. data/test/dummy/config/initializers/mime_types.rb +5 -0
  266. data/test/dummy/config/initializers/secret_token.rb +7 -0
  267. data/test/dummy/config/initializers/session_store.rb +8 -0
  268. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  269. data/test/dummy/config/locales/en.yml +5 -0
  270. data/test/dummy/config/routes.rb +58 -0
  271. data/test/dummy/lib/assets/.gitkeep +0 -0
  272. data/test/dummy/log/.gitkeep +0 -0
  273. data/test/dummy/public/404.html +26 -0
  274. data/test/dummy/public/422.html +26 -0
  275. data/test/dummy/public/500.html +25 -0
  276. data/test/dummy/public/favicon.ico +0 -0
  277. data/test/dummy/script/rails +6 -0
  278. data/test/surveyor-gui_test.rb +7 -0
  279. data/test/test_helper.rb +15 -0
  280. data/vendor/assets/stylesheets/.gitkeep +0 -0
  281. data/vendor/assets/stylesheets/custom.sass +5 -0
  282. data/vendor/plugins/.gitkeep +0 -0
  283. metadata +664 -0
@@ -0,0 +1,90 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Dependency do
4
+ before(:each) do
5
+ @dependency = FactoryGirl.create(:dependency)
6
+ end
7
+
8
+ it "should be valid" do
9
+ @dependency.should be_valid
10
+ end
11
+
12
+ it "should be invalid without a rule" do
13
+ @dependency.rule = nil
14
+ @dependency.should have(2).errors_on(:rule)
15
+ @dependency.rule = " "
16
+ @dependency.should have(1).errors_on(:rule)
17
+ end
18
+
19
+ it "should be invalid without a question_id" do
20
+ @dependency.question_id = nil
21
+ @dependency.should have(1).error_on(:question_id)
22
+
23
+ @dependency.question_group_id = 1
24
+ @dependency.should be_valid
25
+
26
+ @dependency.question_id.should be_nil
27
+ @dependency.question_group_id = nil
28
+ @dependency.should have(1).error_on(:question_group_id)
29
+ end
30
+
31
+ it "should alias question_id as dependent_question_id" do
32
+ @dependency.question_id = 19
33
+ @dependency.dependent_question_id.should == 19
34
+ @dependency.dependent_question_id = 14
35
+ @dependency.question_id.should == 14
36
+ end
37
+
38
+ it "should be invalid unless rule composed of only references and operators" do
39
+ @dependency.rule = "foo"
40
+ @dependency.should have(1).error_on(:rule)
41
+ @dependency.rule = "1 to 2"
42
+ @dependency.should have(1).error_on(:rule)
43
+ @dependency.rule = "a and b"
44
+ @dependency.should have(1).error_on(:rule)
45
+ end
46
+ end
47
+
48
+ describe Dependency, "when evaluating dependency conditions of a question in a response set" do
49
+
50
+ before(:each) do
51
+ @dep = Dependency.new(:rule => "A", :question_id => 1)
52
+ @dep2 = Dependency.new(:rule => "A and B", :question_id => 1)
53
+ @dep3 = Dependency.new(:rule => "A or B", :question_id => 1)
54
+ @dep4 = Dependency.new(:rule => "!(A and B) and C", :question_id => 1)
55
+
56
+ @dep_c = mock_model(DependencyCondition, :id => 1, :rule_key => "A", :to_hash => {:A => true})
57
+ @dep_c2 = mock_model(DependencyCondition, :id => 2, :rule_key => "B", :to_hash => {:B => false})
58
+ @dep_c3 = mock_model(DependencyCondition, :id => 3, :rule_key => "C", :to_hash => {:C => true})
59
+
60
+ @dep.stub(:dependency_conditions).and_return([@dep_c])
61
+ @dep2.stub(:dependency_conditions).and_return([@dep_c, @dep_c2])
62
+ @dep3.stub(:dependency_conditions).and_return([@dep_c, @dep_c2])
63
+ @dep4.stub(:dependency_conditions).and_return([@dep_c, @dep_c2, @dep_c3])
64
+ end
65
+
66
+ it "knows if the dependencies are met" do
67
+ @dep.is_met?(@response_set).should be_true
68
+ @dep2.is_met?(@response_set).should be_false
69
+ @dep3.is_met?(@response_set).should be_true
70
+ @dep4.is_met?(@response_set).should be_true
71
+ end
72
+
73
+ it "returns the proper keyed pairs from the dependency conditions" do
74
+ @dep.conditions_hash(@response_set).should == {:A => true}
75
+ @dep2.conditions_hash(@response_set).should == {:A => true, :B => false}
76
+ @dep3.conditions_hash(@response_set).should == {:A => true, :B => false}
77
+ @dep4.conditions_hash(@response_set).should == {:A => true, :B => false, :C => true}
78
+ end
79
+ end
80
+ describe Dependency, "with conditions" do
81
+ it "should destroy conditions when destroyed" do
82
+ @dependency = Dependency.new(:rule => "A and B and C", :question_id => 1)
83
+ FactoryGirl.create(:dependency_condition, :dependency => @dependency, :rule_key => "A")
84
+ FactoryGirl.create(:dependency_condition, :dependency => @dependency, :rule_key => "B")
85
+ FactoryGirl.create(:dependency_condition, :dependency => @dependency, :rule_key => "C")
86
+ dc_ids = @dependency.dependency_conditions.map(&:id)
87
+ @dependency.destroy
88
+ dc_ids.each{|id| DependencyCondition.find_by_id(id).should == nil}
89
+ end
90
+ end
@@ -0,0 +1,66 @@
1
+ # encoding: UTF-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3
+
4
+ describe QuestionGroup do
5
+ let(:question_group){ FactoryGirl.create(:question_group) }
6
+ let(:dependency){ FactoryGirl.create(:dependency) }
7
+ let(:response_set){ FactoryGirl.create(:response_set) }
8
+
9
+ context "when creating" do
10
+ it { question_group.should be_valid }
11
+ it "#display_type = inline by default" do
12
+ question_group.display_type = "inline"
13
+ question_group.renderer.should == :inline
14
+ end
15
+ it "#renderer == 'default' when #display_type = nil" do
16
+ question_group.display_type = nil
17
+ question_group.renderer.should == :default
18
+ end
19
+ it "interprets symbolizes #display_type to #renderer" do
20
+ question_group.display_type = "foo"
21
+ question_group.renderer.should == :foo
22
+ end
23
+ it "reports DOM ready #css_class based on dependencies" do
24
+ question_group.dependency = dependency
25
+ dependency.should_receive(:is_met?).and_return(true)
26
+ question_group.css_class(response_set).should == "g_dependent"
27
+
28
+ dependency.should_receive(:is_met?).and_return(false)
29
+ question_group.css_class(response_set).should == "g_dependent g_hidden"
30
+
31
+ question_group.custom_class = "foo bar"
32
+ dependency.should_receive(:is_met?).and_return(false)
33
+ question_group.css_class(response_set).should == "g_dependent g_hidden foo bar"
34
+ end
35
+ end
36
+
37
+ context "with translations" do
38
+ require 'yaml'
39
+ let(:survey){ FactoryGirl.create(:survey) }
40
+ let(:survey_section){ FactoryGirl.create(:survey_section) }
41
+ let(:survey_translation){
42
+ FactoryGirl.create(:survey_translation, :locale => :es, :translation => {
43
+ :question_groups => {
44
+ :goodbye => {
45
+ :text => "¡Adios!"
46
+ }
47
+ }
48
+ }.to_yaml)
49
+ }
50
+ let(:question){ FactoryGirl.create(:question) }
51
+ before do
52
+ question_group.text = "Goodbye"
53
+ question_group.reference_identifier = "goodbye"
54
+ question_group.questions = [question]
55
+ question.survey_section = survey_section
56
+ survey_section.survey = survey
57
+ survey.translations << survey_translation
58
+ end
59
+ it "returns its own translation" do
60
+ question_group.translation(:es)[:text].should == "¡Adios!"
61
+ end
62
+ it "returns its own default values" do
63
+ question_group.translation(:de).should == {"text" => "Goodbye", "help_text" => nil}
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,176 @@
1
+ # encoding: UTF-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3
+
4
+ describe Question do
5
+ let(:question){ FactoryGirl.create(:question) }
6
+
7
+ context "when creating" do
8
+ it "is invalid without #text" do
9
+ question.text = nil
10
+ question.should have(1).error_on :text
11
+ end
12
+ it "#is_mandantory == false by default" do
13
+ question.mandatory?.should be_false
14
+ end
15
+ it "converts #pick to string" do
16
+ question.pick.should == "none"
17
+ question.pick = :one
18
+ question.pick.should == "one"
19
+ question.pick = nil
20
+ question.pick.should == nil
21
+ end
22
+ it "#renderer == 'default' when #display_type = nil" do
23
+ question.display_type = nil
24
+ question.renderer.should == :default
25
+ end
26
+ it "has #api_id with 36 characters by default" do
27
+ question.api_id.length.should == 36
28
+ end
29
+ it "#part_of_group? and #solo? are aware of question groups" do
30
+ question.question_group = FactoryGirl.create(:question_group)
31
+ question.solo?.should be_false
32
+ question.part_of_group?.should be_true
33
+
34
+ question.question_group = nil
35
+ question.solo?.should be_true
36
+ question.part_of_group?.should be_false
37
+ end
38
+ end
39
+
40
+ context "with answers" do
41
+ let(:answer_1){ FactoryGirl.create(:answer, :question => question, :display_order => 3, :text => "blue")}
42
+ let(:answer_2){ FactoryGirl.create(:answer, :question => question, :display_order => 1, :text => "red")}
43
+ let(:answer_3){ FactoryGirl.create(:answer, :question => question, :display_order => 2, :text => "green")}
44
+ before do
45
+ [answer_1, answer_2, answer_3].each{|a| question.answers << a }
46
+ end
47
+ it{ question.should have(3).answers}
48
+ it "gets answers in order" do
49
+ question.answers.order("display_order asc").should == [answer_2, answer_3, answer_1]
50
+ question.answers.order("display_order asc").map(&:display_order).should == [1,2,3]
51
+ end
52
+ it "deletes child answers when deleted" do
53
+ answer_ids = question.answers.map(&:id)
54
+ question.destroy
55
+ answer_ids.each{|id| Answer.find_by_id(id).should be_nil}
56
+ end
57
+ end
58
+
59
+ context "with dependencies" do
60
+ let(:response_set){ FactoryGirl.create(:response_set) }
61
+ let(:dependency){ FactoryGirl.create(:dependency) }
62
+ before do
63
+ question.dependency = dependency
64
+ dependency.stub(:is_met?).with(response_set).and_return true
65
+ end
66
+ it "checks its dependency" do
67
+ question.triggered?(response_set).should be_true
68
+ end
69
+ it "deletes its dependency when deleted" do
70
+ d_id = question.dependency.id
71
+ question.destroy
72
+ Dependency.find_by_id(d_id).should be_nil
73
+ end
74
+ end
75
+
76
+ context "with mustache text substitution" do
77
+ require 'mustache'
78
+ let(:mustache_context){ Class.new(::Mustache){ def site; "Northwestern"; end; def foo; "bar"; end } }
79
+ it "subsitutes Mustache context variables" do
80
+ question.text = "You are in {{site}}"
81
+ question.in_context(question.text, mustache_context).should == "You are in Northwestern"
82
+ end
83
+ it "substitues in views" do
84
+ question.text = "You are in {{site}}"
85
+ question.text_for(nil, mustache_context).should == "You are in Northwestern"
86
+ end
87
+ end
88
+
89
+ context "with translations" do
90
+ require 'yaml'
91
+ let(:survey){ FactoryGirl.create(:survey) }
92
+ let(:survey_section){ FactoryGirl.create(:survey_section) }
93
+ let(:survey_translation){
94
+ FactoryGirl.create(:survey_translation, :locale => :es, :translation => {
95
+ :questions => {
96
+ :hello => {
97
+ :text => "¡Hola!"
98
+ }
99
+ }
100
+ }.to_yaml)
101
+ }
102
+ before do
103
+ question.reference_identifier = "hello"
104
+ question.survey_section = survey_section
105
+ survey_section.survey = survey
106
+ survey.translations << survey_translation
107
+ end
108
+ it "returns its own translation" do
109
+ YAML.load(survey_translation.translation).should_not be_nil
110
+ question.translation(:es)[:text].should == "¡Hola!"
111
+ end
112
+ it "returns its own default values" do
113
+ question.translation(:de).should == {"text" => question.text, "help_text" => question.help_text}
114
+ end
115
+ it "returns translations in views" do
116
+ question.text_for(nil, nil, :es).should == "¡Hola!"
117
+ end
118
+ it "returns default values in views" do
119
+ question.text_for(nil, nil, :de).should == question.text
120
+ end
121
+ end
122
+
123
+ context "handling strings" do
124
+ it "#split preserves strings" do
125
+ question.split(question.text).should == "What is your favorite color?"
126
+ end
127
+ it "#split(:pre) preserves strings" do
128
+ question.split(question.text, :pre).should == "What is your favorite color?"
129
+ end
130
+ it "#split(:post) preserves strings" do
131
+ question.split(question.text, :post).should == ""
132
+ end
133
+ it "#split splits strings" do
134
+ question.text = "before|after|extra"
135
+ question.split(question.text).should == "before|after|extra"
136
+ end
137
+ it "#split(:pre) splits strings" do
138
+ question.text = "before|after|extra"
139
+ question.split(question.text, :pre).should == "before"
140
+ end
141
+ it "#split(:post) splits strings" do
142
+ question.text = "before|after|extra"
143
+ question.split(question.text, :post).should == "after|extra"
144
+ end
145
+ end
146
+
147
+ context "for views" do
148
+ it "#text_for with #display_type == image" do
149
+ question.text = "rails.png"
150
+ question.display_type = :image
151
+ question.text_for.should =~ /<img alt="Rails" src="\/(images|assets)\/rails\.png" \/>/
152
+ end
153
+ it "#help_text_for"
154
+ it "#text_for preserves strings" do
155
+ question.text_for.should == "What is your favorite color?"
156
+ end
157
+ it "#text_for(:pre) preserves strings" do
158
+ question.text_for(:pre).should == "What is your favorite color?"
159
+ end
160
+ it "#text_for(:post) preserves strings" do
161
+ question.text_for(:post).should == ""
162
+ end
163
+ it "#text_for splits strings" do
164
+ question.text = "before|after|extra"
165
+ question.text_for.should == "before|after|extra"
166
+ end
167
+ it "#text_for(:pre) splits strings" do
168
+ question.text = "before|after|extra"
169
+ question.text_for(:pre).should == "before"
170
+ end
171
+ it "#text_for(:post) splits strings" do
172
+ question.text = "before|after|extra"
173
+ question.text_for(:post).should == "after|extra"
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,452 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ResponseSet do
4
+ let(:response_set) { FactoryGirl.create(:response_set) }
5
+
6
+ before(:each) do
7
+ @response_set = FactoryGirl.create(:response_set)
8
+ @radio_response_attributes = HashWithIndifferentAccess.new({"1"=>{"question_id"=>"1", "answer_id"=>"1", "string_value"=>"XXL"}, "2"=>{"question_id"=>"2", "answer_id"=>"6"}, "3"=>{"question_id"=>"3"}})
9
+ @checkbox_response_attributes = HashWithIndifferentAccess.new({"1"=>{"question_id"=>"9", "answer_id"=>"11"}, "2"=>{"question_id"=>"9", "answer_id"=>"12"}})
10
+ @other_response_attributes = HashWithIndifferentAccess.new({"6"=>{"question_id"=>"6", "answer_id" => "3", "string_value"=>""}, "7"=>{"question_id"=>"7", "answer_id" => "4", "text_value"=>"Brian is tired"}, "5"=>{"question_id"=>"5", "answer_id" => "5", "string_value"=>""}})
11
+ end
12
+
13
+ it "should have a unique code with length 10 that identifies the survey" do
14
+ @response_set.access_code.should_not be_nil
15
+ @response_set.access_code.length.should == 10
16
+ end
17
+
18
+ describe '#access_code' do
19
+ let!(:rs1) { FactoryGirl.create(:response_set).tap { |rs| rs.update_attribute(:access_code, 'one') } }
20
+ let!(:rs2) { FactoryGirl.create(:response_set).tap { |rs| rs.update_attribute(:access_code, 'two') } }
21
+
22
+ # Regression test for #263
23
+ it 'accepts an access code in the constructor' do
24
+ rs = ResponseSet.new
25
+ rs.access_code = 'eleven'
26
+ rs.access_code.should == 'eleven'
27
+ end
28
+
29
+ # Regression test for #263
30
+ it 'setter accepts a conflicting access code' do
31
+ rs2.access_code = 'one'
32
+ rs2.access_code.should == 'one'
33
+ end
34
+
35
+ it 'is invalid when conflicting' do
36
+ rs2.access_code = 'one'
37
+ rs2.should_not be_valid
38
+ rs2.should have(1).errors_on(:access_code)
39
+ end
40
+ end
41
+
42
+ it "is completable" do
43
+ @response_set.completed_at.should be_nil
44
+ @response_set.complete!
45
+ @response_set.completed_at.should_not be_nil
46
+ @response_set.completed_at.is_a?(Time).should be_true
47
+ @response_set.should be_complete
48
+ end
49
+
50
+ it 'saves its responses' do
51
+ new_set = ResponseSet.new(:survey => FactoryGirl.create(:survey))
52
+ new_set.responses.build(:question_id => 1, :answer_id => 1, :string_value => 'XXL')
53
+ new_set.save!
54
+
55
+ ResponseSet.find(new_set.id).responses.should have(1).items
56
+ end
57
+
58
+ describe '#update_from_ui_hash' do
59
+ let(:ui_hash) { {} }
60
+ let(:api_id) { 'ABCDEF-1234-567890' }
61
+
62
+ let(:question_id) { 42 }
63
+ let(:answer_id) { 137 }
64
+
65
+ def ui_response(attrs={})
66
+ { 'question_id' => question_id.to_s, 'api_id' => api_id }.merge(attrs)
67
+ end
68
+
69
+ def do_ui_update
70
+ response_set.update_from_ui_hash(ui_hash)
71
+ end
72
+
73
+ def resulting_response
74
+ # response_set_id criterion is to make sure a created response is
75
+ # appropriately associated.
76
+ Response.where(:api_id => api_id, :response_set_id => response_set).first
77
+ end
78
+
79
+ shared_examples 'pick one or any' do
80
+ it 'saves an answer alone' do
81
+ ui_hash['3'] = ui_response('answer_id' => set_answer_id)
82
+ do_ui_update
83
+ resulting_response.answer_id.should == answer_id
84
+ end
85
+
86
+ it 'preserves the question' do
87
+ ui_hash['4'] = ui_response('answer_id' => set_answer_id)
88
+ do_ui_update
89
+ resulting_response.question_id.should == question_id
90
+ end
91
+
92
+ it 'interprets a blank answer as no response' do
93
+ ui_hash['7'] = ui_response('answer_id' => blank_answer_id)
94
+ do_ui_update
95
+ resulting_response.should be_nil
96
+ end
97
+
98
+ it 'interprets no answer_id as no response' do
99
+ ui_hash['8'] = ui_response
100
+ do_ui_update
101
+ resulting_response.should be_nil
102
+ end
103
+
104
+ [
105
+ ['string_value', 'foo', '', 'foo'],
106
+ ['datetime_value', '2010-10-01 17:15', '', Time.zone.parse('2010-10-1 17:15')],
107
+ ['date_value', '2010-10-01', '', '2010-10-01'],
108
+ ['time_value', '17:15', '', '17:15'],
109
+ ['integer_value', '9', '', 9],
110
+ ['float_value', '4.0', '', 4.0],
111
+ ['text_value', 'more than foo', '', 'more than foo']
112
+ ].each do |value_type, set_value, blank_value, expected_value|
113
+ describe "plus #{value_type}" do
114
+ it 'saves the value' do
115
+ ui_hash['11'] = ui_response('answer_id' => set_answer_id, value_type => set_value)
116
+ do_ui_update
117
+ resulting_response.send(value_type).should == expected_value
118
+ end
119
+
120
+ it 'interprets a blank answer as no response' do
121
+ ui_hash['18'] = ui_response('answer_id' => blank_answer_id, value_type => set_value)
122
+ do_ui_update
123
+ resulting_response.should be_nil
124
+ end
125
+
126
+ it 'interprets a blank value as no response' do
127
+ ui_hash['29'] = ui_response('answer_id' => set_answer_id, value_type => blank_value)
128
+ do_ui_update
129
+ resulting_response.should be_nil
130
+ end
131
+
132
+ it 'interprets no answer_id as no response' do
133
+ ui_hash['8'] = ui_response(value_type => set_value)
134
+ do_ui_update
135
+ resulting_response.should be_nil
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ shared_examples 'response interpretation' do
142
+ it 'fails when api_id is not provided' do
143
+ ui_hash['0'] = { 'question_id' => question_id }
144
+ lambda { do_ui_update }.should raise_error(/api_id missing from response 0/)
145
+ end
146
+
147
+ describe 'for a radio button' do
148
+ let(:set_answer_id) { answer_id.to_s }
149
+ let(:blank_answer_id) { '' }
150
+
151
+ include_examples 'pick one or any'
152
+ end
153
+
154
+ describe 'for a checkbox' do
155
+ let(:set_answer_id) { ['', answer_id.to_s] }
156
+ let(:blank_answer_id) { [''] }
157
+
158
+ include_examples 'pick one or any'
159
+ end
160
+ end
161
+
162
+ describe 'with a new response' do
163
+ include_examples 'response interpretation'
164
+
165
+ # After much effort I cannot produce this situation in a test, either with
166
+ # with threads or separate processes. While SQLite 3 will nominally allow
167
+ # for some coarse-grained concurrency, it does not appear to work with
168
+ # simultaneous write transactions the way AR uses SQLite. Instead,
169
+ # simultaneous write transactions always result in a
170
+ # SQLite3::BusyException, regardless of the connection's timeout setting.
171
+ it 'fails predicably when another response with the same api_id is created in a simultaneous open transaction'
172
+ end
173
+
174
+ describe 'with an existing response' do
175
+ let!(:original_response) {
176
+ response_set.responses.build(:question_id => question_id, :answer_id => answer_id).tap do |r|
177
+ r.api_id = api_id # not mass assignable
178
+ r.save!
179
+ end
180
+ }
181
+
182
+ include_examples 'response interpretation'
183
+
184
+ it 'fails when the existing response is for a different question' do
185
+ ui_hash['76'] = ui_response('question_id' => '43', 'answer_id' => answer_id.to_s)
186
+
187
+ lambda { do_ui_update }.should raise_error(/Illegal attempt to change question for response #{api_id}./)
188
+ end
189
+ end
190
+
191
+ # clean_with_truncation is necessary because AR 3.0 can't roll back a nested
192
+ # transaction with SQLite.
193
+ it 'rolls back all changes on failure', :clean_with_truncation do
194
+ ui_hash['0'] = ui_response('question_id' => '42', 'answer_id' => answer_id.to_s)
195
+ ui_hash['1'] = { 'answer_id' => '7' } # no api_id
196
+
197
+ begin
198
+ do_ui_update
199
+ fail "Expected error did not occur"
200
+ rescue
201
+ end
202
+
203
+ response_set.reload.responses.should be_empty
204
+ end
205
+ end
206
+ end
207
+
208
+ describe ResponseSet, "with dependencies" do
209
+ before(:each) do
210
+ @section = FactoryGirl.create(:survey_section)
211
+ # Questions
212
+ @do_you_like_pie = FactoryGirl.create(:question, :text => "Do you like pie?", :survey_section => @section)
213
+ @what_flavor = FactoryGirl.create(:question, :text => "What flavor?", :survey_section => @section)
214
+ @what_bakery = FactoryGirl.create(:question, :text => "What bakery?", :survey_section => @section)
215
+ # Answers
216
+ @do_you_like_pie.answers << FactoryGirl.create(:answer, :text => "yes", :question_id => @do_you_like_pie.id)
217
+ @do_you_like_pie.answers << FactoryGirl.create(:answer, :text => "no", :question_id => @do_you_like_pie.id)
218
+ @what_flavor.answers << FactoryGirl.create(:answer, :response_class => :string, :question_id => @what_flavor.id)
219
+ @what_bakery.answers << FactoryGirl.create(:answer, :response_class => :string, :question_id => @what_bakery.id)
220
+ # Dependency
221
+ @what_flavor_dep = FactoryGirl.create(:dependency, :rule => "A", :question_id => @what_flavor.id)
222
+ FactoryGirl.create(:dependency_condition, :rule_key => "A", :question_id => @do_you_like_pie.id, :operator => "==", :answer_id => @do_you_like_pie.answers.first.id, :dependency_id => @what_flavor_dep.id)
223
+ @what_bakery_dep = FactoryGirl.create(:dependency, :rule => "B", :question_id => @what_bakery.id)
224
+ FactoryGirl.create(:dependency_condition, :rule_key => "B", :question_id => @do_you_like_pie.id, :operator => "==", :answer_id => @do_you_like_pie.answers.first.id, :dependency_id => @what_bakery_dep.id)
225
+ # Responses
226
+ @response_set = FactoryGirl.create(:response_set)
227
+ @response_set.responses << FactoryGirl.create(:response, :question_id => @do_you_like_pie.id, :answer_id => @do_you_like_pie.answers.first.id, :response_set_id => @response_set.id)
228
+ @response_set.responses << FactoryGirl.create(:response, :string_value => "pecan pie", :question_id => @what_flavor.id, :answer_id => @what_flavor.answers.first.id, :response_set_id => @response_set.id)
229
+ end
230
+
231
+ it "should list unanswered dependencies to show at the top of the next page (javascript turned off)" do
232
+ @response_set.unanswered_dependencies.should == [@what_bakery]
233
+ end
234
+ it "should list answered and unanswered dependencies to show inline (javascript turned on)" do
235
+ @response_set.all_dependencies[:show].should == ["q_#{@what_flavor.id}", "q_#{@what_bakery.id}"]
236
+ end
237
+ it "should list group as dependency" do
238
+ # Question Group
239
+ crust_group = FactoryGirl.create(:question_group, :text => "Favorite Crusts")
240
+
241
+ # Question
242
+ what_crust = FactoryGirl.create(:question, :text => "What is your favorite curst type?", :survey_section => @section)
243
+ crust_group.questions << what_crust
244
+
245
+ # Answers
246
+ what_crust.answers << FactoryGirl.create(:answer, :response_class => :string, :question_id => what_crust.id)
247
+
248
+ # Dependency
249
+ crust_group_dep = FactoryGirl.create(:dependency, :rule => "C", :question_group_id => crust_group.id, :question => nil)
250
+ FactoryGirl.create(:dependency_condition, :rule_key => "C", :question_id => @do_you_like_pie.id, :operator => "==", :answer_id => @do_you_like_pie.answers.first.id, :dependency_id => crust_group_dep.id)
251
+
252
+ @response_set.unanswered_dependencies.should == [@what_bakery, crust_group]
253
+ end
254
+ end
255
+ describe ResponseSet, "dependency_conditions" do
256
+ before do
257
+ @section = FactoryGirl.create(:survey_section)
258
+ # Questions
259
+ @like_pie = FactoryGirl.create(:question, :text => "Do you like pie?", :survey_section => @section)
260
+ @like_jam = FactoryGirl.create(:question, :text => "Do you like jam?", :survey_section => @section)
261
+ @what_is_wrong_with_you = FactoryGirl.create(:question, :text => "What's wrong with you?", :survey_section => @section)
262
+ # Answers
263
+ @like_pie.answers << FactoryGirl.create(:answer, :text => "yes", :question_id => @like_pie.id)
264
+ @like_pie.answers << FactoryGirl.create(:answer, :text => "no", :question_id => @like_pie.id)
265
+ @like_jam.answers << FactoryGirl.create(:answer, :text => "yes", :question_id => @like_jam.id)
266
+ @like_jam.answers << FactoryGirl.create(:answer, :text => "no", :question_id => @like_jam.id)
267
+ # Dependency
268
+ @what_is_wrong_with_you = FactoryGirl.create(:dependency, :rule => "A or B", :question_id => @what_is_wrong_with_you.id)
269
+ @dep_a = FactoryGirl.create(:dependency_condition, :rule_key => "A", :question_id => @like_pie.id, :operator => "==", :answer_id => @like_pie.answers.first.id, :dependency_id => @what_is_wrong_with_you.id)
270
+ @dep_b = FactoryGirl.create(:dependency_condition, :rule_key => "B", :question_id => @like_jam.id, :operator => "==", :answer_id => @like_jam.answers.first.id, :dependency_id => @what_is_wrong_with_you.id)
271
+ # Responses
272
+ @response_set = FactoryGirl.create(:response_set)
273
+ @response_set.responses << FactoryGirl.create(:response, :question_id => @like_pie.id, :answer_id => @like_pie.answers.last.id, :response_set_id => @response_set.id)
274
+ end
275
+ it "should list all dependencies for answered questions" do
276
+ dependency_conditions = @response_set.send(:dependencies).last.dependency_conditions
277
+ dependency_conditions.size.should == 2
278
+ dependency_conditions.should include(@dep_a)
279
+ dependency_conditions.should include(@dep_b)
280
+
281
+ end
282
+ it "should list all dependencies for passed question_id" do
283
+ # Questions
284
+ like_ice_cream = FactoryGirl.create(:question, :text => "Do you like ice_cream?", :survey_section => @section)
285
+ what_flavor = FactoryGirl.create(:question, :text => "What flavor?", :survey_section => @section)
286
+ # Answers
287
+ like_ice_cream.answers << FactoryGirl.create(:answer, :text => "yes", :question_id => like_ice_cream.id)
288
+ like_ice_cream.answers << FactoryGirl.create(:answer, :text => "no", :question_id => like_ice_cream.id)
289
+ what_flavor.answers << FactoryGirl.create(:answer, :response_class => :string, :question_id => what_flavor.id)
290
+ # Dependency
291
+ flavor_dependency = FactoryGirl.create(:dependency, :rule => "C", :question_id => what_flavor.id)
292
+ flavor_dependency_condition = FactoryGirl.create(:dependency_condition, :rule_key => "A", :question_id => like_ice_cream.id, :operator => "==",
293
+ :answer_id => like_ice_cream.answers.first.id, :dependency_id => flavor_dependency.id)
294
+ # Responses
295
+ dependency_conditions = @response_set.send(:dependencies, like_ice_cream.id).should == [flavor_dependency]
296
+ end
297
+ end
298
+
299
+ describe ResponseSet, "as a quiz" do
300
+ before(:each) do
301
+ @survey = FactoryGirl.create(:survey)
302
+ @section = FactoryGirl.create(:survey_section, :survey => @survey)
303
+ @response_set = FactoryGirl.create(:response_set, :survey => @survey)
304
+ end
305
+ def generate_responses(count, quiz = nil, correct = nil)
306
+ count.times do |i|
307
+ q = FactoryGirl.create(:question, :survey_section => @section)
308
+ a = FactoryGirl.create(:answer, :question => q, :response_class => "answer")
309
+ x = FactoryGirl.create(:answer, :question => q, :response_class => "answer")
310
+ q.correct_answer = (quiz == "quiz" ? a : nil)
311
+ @response_set.responses << FactoryGirl.create(:response, :question => q, :answer => (correct == "correct" ? a : x))
312
+ end
313
+ end
314
+
315
+ it "should report correctness if it is a quiz" do
316
+ generate_responses(3, "quiz", "correct")
317
+ @response_set.correct?.should be_true
318
+ @response_set.correctness_hash.should == {:questions => 3, :responses => 3, :correct => 3}
319
+ end
320
+ it "should report incorrectness if it is a quiz" do
321
+ generate_responses(3, "quiz", "incorrect")
322
+ @response_set.correct?.should be_false
323
+ @response_set.correctness_hash.should == {:questions => 3, :responses => 3, :correct => 0}
324
+ end
325
+ it "should report correct if it isn't a quiz" do
326
+ generate_responses(3, "non-quiz")
327
+ @response_set.correct?.should be_true
328
+ @response_set.correctness_hash.should == {:questions => 3, :responses => 3, :correct => 3}
329
+ end
330
+ end
331
+ describe ResponseSet, "with mandatory questions" do
332
+ before(:each) do
333
+ @survey = FactoryGirl.create(:survey)
334
+ @section = FactoryGirl.create(:survey_section, :survey => @survey)
335
+ @response_set = FactoryGirl.create(:response_set, :survey => @survey)
336
+ end
337
+ def generate_responses(count, mandatory = nil, responded = nil)
338
+ count.times do |i|
339
+ q = FactoryGirl.create(:question, :survey_section => @section, :is_mandatory => (mandatory == "mandatory"))
340
+ a = FactoryGirl.create(:answer, :question => q, :response_class => "answer")
341
+ if responded == "responded"
342
+ @response_set.responses << FactoryGirl.create(:response, :question => q, :answer => a)
343
+ end
344
+ end
345
+ end
346
+ it "should report progress without mandatory questions" do
347
+ generate_responses(3)
348
+ @response_set.mandatory_questions_complete?.should be_true
349
+ @response_set.progress_hash.should == {:questions => 3, :triggered => 3, :triggered_mandatory => 0, :triggered_mandatory_completed => 0}
350
+ end
351
+ it "should report progress with mandatory questions" do
352
+ generate_responses(3, "mandatory", "responded")
353
+ @response_set.mandatory_questions_complete?.should be_true
354
+ @response_set.progress_hash.should == {:questions => 3, :triggered => 3, :triggered_mandatory => 3, :triggered_mandatory_completed => 3}
355
+ end
356
+ it "should report progress with mandatory questions" do
357
+ generate_responses(3, "mandatory", "not-responded")
358
+ @response_set.mandatory_questions_complete?.should be_false
359
+ @response_set.progress_hash.should == {:questions => 3, :triggered => 3, :triggered_mandatory => 3, :triggered_mandatory_completed => 0}
360
+ end
361
+ it "should ignore labels and images" do
362
+ generate_responses(3, "mandatory", "responded")
363
+ FactoryGirl.create(:question, :survey_section => @section, :display_type => "label", :is_mandatory => true)
364
+ FactoryGirl.create(:question, :survey_section => @section, :display_type => "image", :is_mandatory => true)
365
+ @response_set.mandatory_questions_complete?.should be_true
366
+ @response_set.progress_hash.should == {:questions => 5, :triggered => 5, :triggered_mandatory => 5, :triggered_mandatory_completed => 5}
367
+ end
368
+ end
369
+ describe ResponseSet, "with mandatory, dependent questions" do
370
+ before(:each) do
371
+ @survey = FactoryGirl.create(:survey)
372
+ @section = FactoryGirl.create(:survey_section, :survey => @survey)
373
+ Qstruct = Struct.new(:q, :a) unless defined?(Qstruct)
374
+ end
375
+ def generate_responses(count, mandatory = nil, dependent = nil, triggered = nil)
376
+ dq = FactoryGirl.create(:question, :survey_section => @section, :is_mandatory => (mandatory == "mandatory"))
377
+ da = FactoryGirl.create(:answer, :question => dq, :response_class => "answer")
378
+ dx = FactoryGirl.create(:answer, :question => dq, :response_class => "answer")
379
+ qary = []
380
+ count.times do |i|
381
+ q = FactoryGirl.create(:question, :survey_section => @section, :is_mandatory => (mandatory == "mandatory"))
382
+ a = FactoryGirl.create(:answer, :question => q, :response_class => "answer")
383
+ if dependent == "dependent"
384
+ d = FactoryGirl.create(:dependency, :question => q)
385
+ dc = FactoryGirl.create(:dependency_condition, :dependency => d, :question_id => dq.id, :answer_id => da.id)
386
+ end
387
+ qary << Qstruct.new(q, a)
388
+ end
389
+ @response_set = FactoryGirl.create(:response_set, :survey => @survey)
390
+ @response_set.responses << FactoryGirl.create(:response, :response_set => @response_set, :question => dq, :answer => (triggered == "triggered" ? da : dx))
391
+ qary.each do |q|
392
+ @response_set.responses << FactoryGirl.create(:response, :response_set => @response_set, :question => q.q, :answer => q.a)
393
+ end
394
+ end
395
+ it "should report progress without mandatory questions" do
396
+ generate_responses(3, "mandatory", "dependent")
397
+ @response_set.mandatory_questions_complete?.should be_true
398
+ @response_set.progress_hash.should == {:questions => 4, :triggered => 1, :triggered_mandatory => 1, :triggered_mandatory_completed => 1}
399
+ end
400
+ it "should report progress with mandatory questions" do
401
+ generate_responses(3, "mandatory", "dependent", "triggered")
402
+ @response_set.mandatory_questions_complete?.should be_true
403
+ @response_set.progress_hash.should == {:questions => 4, :triggered => 4, :triggered_mandatory => 4, :triggered_mandatory_completed => 4}
404
+ end
405
+ end
406
+ describe ResponseSet, "exporting csv" do
407
+ before(:each) do
408
+ @section = FactoryGirl.create(:survey_section)
409
+ # Questions
410
+ @do_you_like_pie = FactoryGirl.create(:question, :text => "Do you like pie?", :survey_section => @section)
411
+ @what_flavor = FactoryGirl.create(:question, :text => "What flavor?", :survey_section => @section)
412
+ @what_bakery = FactoryGirl.create(:question, :text => "What bakery?", :survey_section => @section)
413
+ # Answers
414
+ @do_you_like_pie.answers << FactoryGirl.create(:answer, :text => "yes", :question_id => @do_you_like_pie.id)
415
+ @do_you_like_pie.answers << FactoryGirl.create(:answer, :text => "no", :question_id => @do_you_like_pie.id)
416
+ @what_flavor.answers << FactoryGirl.create(:answer, :response_class => :string, :question_id => @what_flavor.id)
417
+ @what_bakery.answers << FactoryGirl.create(:answer, :response_class => :string, :question_id => @what_bakery.id)
418
+ # Responses
419
+ @response_set = FactoryGirl.create(:response_set)
420
+ @response_set.responses << FactoryGirl.create(:response, :question_id => @do_you_like_pie.id, :answer_id => @do_you_like_pie.answers.first.id, :response_set_id => @response_set.id)
421
+ @response_set.responses << FactoryGirl.create(:response, :string_value => "pecan pie", :question_id => @what_flavor.id, :answer_id => @what_flavor.answers.first.id, :response_set_id => @response_set.id)
422
+ end
423
+ it "should export a string with responses" do
424
+ @response_set.responses.size.should == 2
425
+ csv = @response_set.to_csv
426
+ csv.is_a?(String).should be_true
427
+ csv.should match "question.short_text"
428
+ csv.should match "What flavor?"
429
+ csv.should match /pecan pie/
430
+ end
431
+ end
432
+
433
+ describe ResponseSet, "#as_json" do
434
+ let(:rs) {
435
+ FactoryGirl.create(:response_set, :responses => [
436
+ FactoryGirl.create(:response, :question => FactoryGirl.create(:question), :answer => FactoryGirl.create(:answer), :string_value => '2')])
437
+ }
438
+
439
+ let(:js) {rs.as_json}
440
+
441
+ it "should include uuid, survey_id" do
442
+ js[:uuid].should == rs.api_id
443
+ end
444
+
445
+ it "should include responses with uuid, question_id, answer_id, value" do
446
+ r0 = rs.responses[0]
447
+ js[:responses][0][:uuid].should == r0.api_id
448
+ js[:responses][0][:answer_id].should == r0.answer.api_id
449
+ js[:responses][0][:question_id].should == r0.question.api_id
450
+ js[:responses][0][:value].should == r0.string_value
451
+ end
452
+ end