hssc_surveyor 1.4.1.pre

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 (212) hide show
  1. data/.gitignore +28 -0
  2. data/.rspec +1 -0
  3. data/CHANGELOG.md +198 -0
  4. data/Gemfile +5 -0
  5. data/Gemfile.rails_version +19 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +186 -0
  8. data/Rakefile +105 -0
  9. data/app/controllers/surveyor_controller.rb +6 -0
  10. data/app/helpers/results_helper.rb +20 -0
  11. data/app/helpers/survey_form_builder.rb +37 -0
  12. data/app/helpers/surveyor_helper.rb +3 -0
  13. data/app/inputs/quiet_input.rb +5 -0
  14. data/app/inputs/surveyor_check_boxes_input.rb +35 -0
  15. data/app/inputs/surveyor_radio_input.rb +18 -0
  16. data/app/models/answer.rb +4 -0
  17. data/app/models/dependency.rb +4 -0
  18. data/app/models/dependency_condition.rb +4 -0
  19. data/app/models/question.rb +4 -0
  20. data/app/models/question_group.rb +5 -0
  21. data/app/models/response.rb +5 -0
  22. data/app/models/response_set.rb +4 -0
  23. data/app/models/survey.rb +4 -0
  24. data/app/models/survey_section.rb +5 -0
  25. data/app/models/survey_section_sweeper.rb +15 -0
  26. data/app/models/survey_translation.rb +5 -0
  27. data/app/models/validation.rb +4 -0
  28. data/app/models/validation_condition.rb +4 -0
  29. data/app/views/layouts/results.html.erb +13 -0
  30. data/app/views/layouts/surveyor_default.html.erb +12 -0
  31. data/app/views/partials/_answer.html.haml +25 -0
  32. data/app/views/partials/_dependents.html.haml +5 -0
  33. data/app/views/partials/_question.html.haml +41 -0
  34. data/app/views/partials/_question_group.html.haml +44 -0
  35. data/app/views/partials/_section.html.haml +12 -0
  36. data/app/views/partials/_section_menu.html.haml +12 -0
  37. data/app/views/surveyor/edit.html.haml +24 -0
  38. data/app/views/surveyor/export.json.rabl +85 -0
  39. data/app/views/surveyor/new.html.haml +24 -0
  40. data/app/views/surveyor/show.html.haml +74 -0
  41. data/app/views/surveyor/show.json.rabl +15 -0
  42. data/ci-exec.sh +56 -0
  43. data/config/routes.rb +8 -0
  44. data/cucumber.yml +10 -0
  45. data/doc/REPRESENTATIONS.md +34 -0
  46. data/doc/api_id_schema.json +7 -0
  47. data/doc/question types.png +0 -0
  48. data/doc/response_set_schema.json +54 -0
  49. data/doc/surveyor question combinations.png +0 -0
  50. data/doc/surveyor reject or delete decision matrix.png +0 -0
  51. data/doc/surveyor_models.png +0 -0
  52. data/doc/surveyor_models2.png +0 -0
  53. data/doc/surveyor_timestamp_schema.json +9 -0
  54. data/features/ajax_submissions.feature +140 -0
  55. data/features/export_to_json.feature +344 -0
  56. data/features/internationalization.feature +121 -0
  57. data/features/no_duplicates.feature +110 -0
  58. data/features/show_survey.feature +71 -0
  59. data/features/step_definitions/parser_steps.rb +145 -0
  60. data/features/step_definitions/surveyor_steps.rb +325 -0
  61. data/features/step_definitions/ui_steps.rb +25 -0
  62. data/features/step_definitions/web_steps.rb +225 -0
  63. data/features/support/REDCapDemoDatabase_DataDictionary.csv +127 -0
  64. data/features/support/database_cleaner.rb +16 -0
  65. data/features/support/env.rb +56 -0
  66. data/features/support/hooks.rb +4 -0
  67. data/features/support/paths.rb +39 -0
  68. data/features/support/redcap_new_headers.csv +1 -0
  69. data/features/support/redcap_siblings.csv +1 -0
  70. data/features/support/redcap_whitespace.csv +1 -0
  71. data/features/support/selectors.rb +39 -0
  72. data/features/support/simultaneous_ajax.rb +101 -0
  73. data/features/support/single_quit_selenium_driver.rb +23 -0
  74. data/features/support/slow_updates.rb +18 -0
  75. data/features/surveyor.feature +895 -0
  76. data/features/surveyor_dependencies.feature +476 -0
  77. data/features/surveyor_parser.feature +504 -0
  78. data/features/z_redcap_parser.feature +62 -0
  79. data/lib/assets/images/surveyor/next.gif +0 -0
  80. data/lib/assets/images/surveyor/prev.gif +0 -0
  81. data/lib/assets/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  82. data/lib/assets/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  83. data/lib/assets/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  84. data/lib/assets/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  85. data/lib/assets/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  86. data/lib/assets/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  87. data/lib/assets/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  88. data/lib/assets/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  89. data/lib/assets/images/ui-icons_222222_256x240.png +0 -0
  90. data/lib/assets/images/ui-icons_2e83ff_256x240.png +0 -0
  91. data/lib/assets/images/ui-icons_454545_256x240.png +0 -0
  92. data/lib/assets/images/ui-icons_888888_256x240.png +0 -0
  93. data/lib/assets/images/ui-icons_cd0a0a_256x240.png +0 -0
  94. data/lib/assets/javascripts/surveyor/jquery-1.9.0.js +9555 -0
  95. data/lib/assets/javascripts/surveyor/jquery-ui-1.10.0.custom.js +14850 -0
  96. data/lib/assets/javascripts/surveyor/jquery-ui-timepicker-addon.js +1919 -0
  97. data/lib/assets/javascripts/surveyor/jquery.maskedinput.js +338 -0
  98. data/lib/assets/javascripts/surveyor/jquery.selectToUISlider.js +240 -0
  99. data/lib/assets/javascripts/surveyor/jquery.surveyor.js +156 -0
  100. data/lib/assets/stylesheets/surveyor/jquery-ui-1.10.0.custom.css +1174 -0
  101. data/lib/assets/stylesheets/surveyor/jquery-ui-timepicker-addon.css +11 -0
  102. data/lib/assets/stylesheets/surveyor/reset.css +48 -0
  103. data/lib/assets/stylesheets/surveyor/results.css +125 -0
  104. data/lib/assets/stylesheets/surveyor/ui.slider.extras.css +110 -0
  105. data/lib/assets/stylesheets/surveyor.sass +132 -0
  106. data/lib/generators/surveyor/custom_generator.rb +18 -0
  107. data/lib/generators/surveyor/install_generator.rb +102 -0
  108. data/lib/generators/surveyor/templates/app/assets/javascripts/surveyor_all.js +6 -0
  109. data/lib/generators/surveyor/templates/app/assets/stylesheets/surveyor_all.css +9 -0
  110. data/lib/generators/surveyor/templates/app/controllers/surveyor_controller.rb +40 -0
  111. data/lib/generators/surveyor/templates/app/views/layouts/surveyor_custom.html.erb +13 -0
  112. data/lib/generators/surveyor/templates/config/locales/surveyor_en.yml +19 -0
  113. data/lib/generators/surveyor/templates/config/locales/surveyor_es.yml +19 -0
  114. data/lib/generators/surveyor/templates/config/locales/surveyor_he.yml +19 -0
  115. data/lib/generators/surveyor/templates/config/locales/surveyor_ko.yml +19 -0
  116. data/lib/generators/surveyor/templates/db/migrate/add_api_id_to_question_groups.rb +10 -0
  117. data/lib/generators/surveyor/templates/db/migrate/add_api_ids.rb +14 -0
  118. data/lib/generators/surveyor/templates/db/migrate/add_api_ids_to_response_sets_and_responses.rb +12 -0
  119. data/lib/generators/surveyor/templates/db/migrate/add_attachment_to_response.rb +5 -0
  120. data/lib/generators/surveyor/templates/db/migrate/add_correct_answer_id_to_questions.rb +10 -0
  121. data/lib/generators/surveyor/templates/db/migrate/add_default_value_to_answers.rb +10 -0
  122. data/lib/generators/surveyor/templates/db/migrate/add_display_order_to_surveys.rb +10 -0
  123. data/lib/generators/surveyor/templates/db/migrate/add_display_type_to_answers.rb +14 -0
  124. data/lib/generators/surveyor/templates/db/migrate/add_index_to_response_sets.rb +10 -0
  125. data/lib/generators/surveyor/templates/db/migrate/add_index_to_surveys.rb +10 -0
  126. data/lib/generators/surveyor/templates/db/migrate/add_input_mask_attributes_to_answer.rb +12 -0
  127. data/lib/generators/surveyor/templates/db/migrate/add_section_id_to_responses.rb +12 -0
  128. data/lib/generators/surveyor/templates/db/migrate/add_unique_index_on_access_code_and_version_in_surveys.rb +10 -0
  129. data/lib/generators/surveyor/templates/db/migrate/add_unique_indicies.rb +18 -0
  130. data/lib/generators/surveyor/templates/db/migrate/add_version_to_surveys.rb +10 -0
  131. data/lib/generators/surveyor/templates/db/migrate/api_ids_must_be_unique.rb +23 -0
  132. data/lib/generators/surveyor/templates/db/migrate/create_answers.rb +38 -0
  133. data/lib/generators/surveyor/templates/db/migrate/create_dependencies.rb +23 -0
  134. data/lib/generators/surveyor/templates/db/migrate/create_dependency_conditions.rb +30 -0
  135. data/lib/generators/surveyor/templates/db/migrate/create_question_groups.rb +28 -0
  136. data/lib/generators/surveyor/templates/db/migrate/create_questions.rb +37 -0
  137. data/lib/generators/surveyor/templates/db/migrate/create_response_sets.rb +23 -0
  138. data/lib/generators/surveyor/templates/db/migrate/create_responses.rb +34 -0
  139. data/lib/generators/surveyor/templates/db/migrate/create_survey_sections.rb +30 -0
  140. data/lib/generators/surveyor/templates/db/migrate/create_survey_translations.rb +19 -0
  141. data/lib/generators/surveyor/templates/db/migrate/create_surveys.rb +32 -0
  142. data/lib/generators/surveyor/templates/db/migrate/create_validation_conditions.rb +33 -0
  143. data/lib/generators/surveyor/templates/db/migrate/create_validations.rb +21 -0
  144. data/lib/generators/surveyor/templates/db/migrate/drop_unique_index_on_access_code_in_surveys.rb +10 -0
  145. data/lib/generators/surveyor/templates/db/migrate/update_blank_api_ids_on_question_group.rb +22 -0
  146. data/lib/generators/surveyor/templates/db/migrate/update_blank_versions_on_surveys.rb +13 -0
  147. data/lib/generators/surveyor/templates/surveys/EXTENDING_SURVEYOR.md +52 -0
  148. data/lib/generators/surveyor/templates/surveys/MODIFYING_SURVEYOR.md +91 -0
  149. data/lib/generators/surveyor/templates/surveys/date_survey.rb +16 -0
  150. data/lib/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +284 -0
  151. data/lib/generators/surveyor/templates/surveys/languages.rb +14 -0
  152. data/lib/generators/surveyor/templates/surveys/quiz.rb +11 -0
  153. data/lib/generators/surveyor/templates/surveys/translations/languages.es.yml +18 -0
  154. data/lib/generators/surveyor/templates/surveys/translations/languages.he.yml +18 -0
  155. data/lib/generators/surveyor/templates/surveys/translations/languages.ko.yml +18 -0
  156. data/lib/generators/surveyor/templates/vendor/assets/stylesheets/custom.sass +5 -0
  157. data/lib/surveyor/acts_as_response.rb +17 -0
  158. data/lib/surveyor/common.rb +59 -0
  159. data/lib/surveyor/engine.rb +10 -0
  160. data/lib/surveyor/helpers/asset_pipeline.rb +13 -0
  161. data/lib/surveyor/helpers/formtastic_custom_input.rb +13 -0
  162. data/lib/surveyor/helpers/surveyor_helper_methods.rb +103 -0
  163. data/lib/surveyor/models/answer_methods.rb +86 -0
  164. data/lib/surveyor/models/dependency_condition_methods.rb +72 -0
  165. data/lib/surveyor/models/dependency_methods.rb +60 -0
  166. data/lib/surveyor/models/question_group_methods.rb +61 -0
  167. data/lib/surveyor/models/question_methods.rb +115 -0
  168. data/lib/surveyor/models/response_methods.rb +132 -0
  169. data/lib/surveyor/models/response_set_methods.rb +196 -0
  170. data/lib/surveyor/models/survey_methods.rb +103 -0
  171. data/lib/surveyor/models/survey_section_methods.rb +56 -0
  172. data/lib/surveyor/models/survey_translation_methods.rb +33 -0
  173. data/lib/surveyor/models/validation_condition_methods.rb +56 -0
  174. data/lib/surveyor/models/validation_methods.rb +48 -0
  175. data/lib/surveyor/mustache_context.rb +11 -0
  176. data/lib/surveyor/parser.rb +428 -0
  177. data/lib/surveyor/redcap_parser.rb +289 -0
  178. data/lib/surveyor/surveyor_controller_methods.rb +237 -0
  179. data/lib/surveyor/unparser.rb +147 -0
  180. data/lib/surveyor/version.rb +3 -0
  181. data/lib/surveyor.rb +13 -0
  182. data/lib/tasks/surveyor_tasks.rake +88 -0
  183. data/rails/init.rb +1 -0
  184. data/spec/controllers/surveyor_controller_spec.rb +307 -0
  185. data/spec/factories.rb +161 -0
  186. data/spec/helpers/formtastic_custom_input_spec.rb +15 -0
  187. data/spec/helpers/surveyor_helper_spec.rb +118 -0
  188. data/spec/lib/benchmark_spec.rb +22 -0
  189. data/spec/lib/chinese_survey.rb +14 -0
  190. data/spec/lib/common_spec.rb +34 -0
  191. data/spec/lib/parser_spec.rb +204 -0
  192. data/spec/lib/rake_kitchen_sink.rb +40 -0
  193. data/spec/lib/redcap_parser_spec.rb +75 -0
  194. data/spec/lib/tasks_spec.rake +26 -0
  195. data/spec/lib/unparser_spec.rb +126 -0
  196. data/spec/models/answer_spec.rb +175 -0
  197. data/spec/models/dependency_condition_spec.rb +439 -0
  198. data/spec/models/dependency_spec.rb +101 -0
  199. data/spec/models/question_group_spec.rb +93 -0
  200. data/spec/models/question_spec.rb +207 -0
  201. data/spec/models/response_set_spec.rb +477 -0
  202. data/spec/models/response_spec.rb +218 -0
  203. data/spec/models/survey_section_spec.rb +85 -0
  204. data/spec/models/survey_spec.rb +191 -0
  205. data/spec/models/validation_condition_spec.rb +113 -0
  206. data/spec/models/validation_spec.rb +74 -0
  207. data/spec/rcov.opts +2 -0
  208. data/spec/spec.opts +4 -0
  209. data/spec/spec_helper.rb +56 -0
  210. data/stacktests.sh +65 -0
  211. data/surveyor.gemspec +45 -0
  212. metadata +657 -0
@@ -0,0 +1,207 @@
1
+ # encoding: UTF-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3
+
4
+ describe Question do
5
+ let(:question){ Factory(: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 = Factory(: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
+ it "protects #api_id" do
39
+ saved_attrs = question.attributes
40
+ if defined? ActiveModel::MassAssignmentSecurity::Error
41
+ expect { question.update_attributes(:api_id => "NEW") }.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
42
+ else
43
+ question.attributes = {:api_id => "NEW"} # Rails doesn't return false, but this will be checked in the comparison to saved_attrs
44
+ end
45
+ question.attributes.should == saved_attrs
46
+ end
47
+ it "protects #created_at" do
48
+ saved_attrs = question.attributes
49
+ if defined? ActiveModel::MassAssignmentSecurity::Error
50
+ expect { question.update_attributes(:created_at => 3.days.ago) }.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
51
+ else
52
+ question.attributes = {:created_at => 3.days.ago} # Rails doesn't return false, but this will be checked in the comparison to saved_attrs
53
+ end
54
+ question.attributes.should == saved_attrs
55
+ end
56
+ it "protects #updated_at" do
57
+ saved_attrs = question.attributes
58
+ if defined? ActiveModel::MassAssignmentSecurity::Error
59
+ expect { question.update_attributes(:updated_at => 3.hours.ago) }.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
60
+ else
61
+ question.attributes = {:updated_at => 3.hours.ago} # Rails doesn't return false, but this will be checked in the comparison to saved_attrs
62
+ end
63
+ question.attributes.should == saved_attrs
64
+ end
65
+ end
66
+
67
+ context "with answers" do
68
+ let(:answer_1){ Factory(:answer, :question => question, :display_order => 3, :text => "blue")}
69
+ let(:answer_2){ Factory(:answer, :question => question, :display_order => 1, :text => "red")}
70
+ let(:answer_3){ Factory(:answer, :question => question, :display_order => 2, :text => "green")}
71
+ before do
72
+ [answer_1, answer_2, answer_3].each{|a| question.answers << a }
73
+ end
74
+ it{ question.should have(3).answers}
75
+ it "gets answers in order" do
76
+ question.answers.should == [answer_2, answer_3, answer_1]
77
+ question.answers.map(&:display_order).should == [1,2,3]
78
+ end
79
+ it "deletes child answers when deleted" do
80
+ answer_ids = question.answers.map(&:id)
81
+ question.destroy
82
+ answer_ids.each{|id| Answer.find_by_id(id).should be_nil}
83
+ end
84
+ end
85
+
86
+ context "with dependencies" do
87
+ let(:response_set){ Factory(:response_set) }
88
+ let(:dependency){ Factory(:dependency) }
89
+ before do
90
+ question.dependency = dependency
91
+ dependency.stub!(:is_met?).with(response_set).and_return true
92
+ end
93
+ it "checks its dependency" do
94
+ question.triggered?(response_set).should be_true
95
+ end
96
+ it "deletes its dependency when deleted" do
97
+ d_id = question.dependency.id
98
+ question.destroy
99
+ Dependency.find_by_id(d_id).should be_nil
100
+ end
101
+ end
102
+
103
+ context "with mustache text substitution" do
104
+ require 'mustache'
105
+ let(:mustache_context){ Class.new(::Mustache){ def site; "Northwestern"; end; def foo; "bar"; end } }
106
+ it "subsitutes Mustache context variables" do
107
+ question.text = "You are in {{site}}"
108
+ question.in_context(question.text, mustache_context).should == "You are in Northwestern"
109
+ end
110
+ it "substitues in views" do
111
+ question.text = "You are in {{site}}"
112
+ question.text_for(nil, mustache_context).should == "You are in Northwestern"
113
+ end
114
+ end
115
+
116
+ context "with translations" do
117
+ require 'yaml'
118
+ let(:survey){ Factory(:survey) }
119
+ let(:survey_section){ Factory(:survey_section) }
120
+ let(:survey_translation){
121
+ Factory(:survey_translation, :locale => :es, :translation => {
122
+ :questions => {
123
+ :hello => {
124
+ :text => "¡Hola!"
125
+ }
126
+ }
127
+ }.to_yaml)
128
+ }
129
+ before do
130
+ question.reference_identifier = "hello"
131
+ question.survey_section = survey_section
132
+ survey_section.survey = survey
133
+ survey.translations << survey_translation
134
+ end
135
+ it "returns its own translation" do
136
+ YAML.load(survey_translation.translation).should_not be_nil
137
+ question.translation(:es)[:text].should == "¡Hola!"
138
+ end
139
+ it "returns its own default values" do
140
+ question.translation(:de).should == {"text" => question.text, "help_text" => question.help_text}
141
+ end
142
+ it "returns translations in views" do
143
+ question.text_for(nil, nil, :es).should == "¡Hola!"
144
+ end
145
+ it "returns default values in views" do
146
+ question.text_for(nil, nil, :de).should == question.text
147
+ end
148
+ end
149
+
150
+ context "handling strings" do
151
+ it "#split preserves strings" do
152
+ question.split(question.text).should == "What is your favorite color?"
153
+ end
154
+ it "#split(:pre) preserves strings" do
155
+ question.split(question.text, :pre).should == "What is your favorite color?"
156
+ end
157
+ it "#split(:post) preserves strings" do
158
+ question.split(question.text, :post).should == ""
159
+ end
160
+ it "#split splits strings" do
161
+ question.text = "before|after|extra"
162
+ question.split(question.text).should == "before|after|extra"
163
+ end
164
+ it "#split(:pre) splits strings" do
165
+ question.text = "before|after|extra"
166
+ question.split(question.text, :pre).should == "before"
167
+ end
168
+ it "#split(:post) splits strings" do
169
+ question.text = "before|after|extra"
170
+ question.split(question.text, :post).should == "after|extra"
171
+ end
172
+ end
173
+
174
+ context "for views" do
175
+ let(:asset_directory){ asset_pipeline_enabled? ? "assets" : "images" }
176
+ before do
177
+ ActionController::Base.helpers.config.assets_dir = "public" unless asset_pipeline_enabled?
178
+ end
179
+ it "#text_for with #display_type == image" do
180
+ question.text = "rails.png"
181
+ question.display_type = :image
182
+ question.text_for.should == %(<img alt="Rails" src="/#{asset_directory}/rails.png" />)
183
+ end
184
+ it "#help_text_for"
185
+ it "#text_for preserves strings" do
186
+ question.text_for.should == "What is your favorite color?"
187
+ end
188
+ it "#text_for(:pre) preserves strings" do
189
+ question.text_for(:pre).should == "What is your favorite color?"
190
+ end
191
+ it "#text_for(:post) preserves strings" do
192
+ question.text_for(:post).should == ""
193
+ end
194
+ it "#text_for splits strings" do
195
+ question.text = "before|after|extra"
196
+ question.text_for.should == "before|after|extra"
197
+ end
198
+ it "#text_for(:pre) splits strings" do
199
+ question.text = "before|after|extra"
200
+ question.text_for(:pre).should == "before"
201
+ end
202
+ it "#text_for(:post) splits strings" do
203
+ question.text = "before|after|extra"
204
+ question.text_for(:post).should == "after|extra"
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,477 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ResponseSet do
4
+ let(:response_set) { Factory(:response_set) }
5
+
6
+ before(:each) do
7
+ @response_set = Factory(: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
+ it "should protect api_id, timestamps, access_code, started_at, completed_at" do
19
+ saved_attrs = @response_set.attributes
20
+ if defined? ActiveModel::MassAssignmentSecurity::Error
21
+ lambda {@response_set.update_attributes(:created_at => 3.days.ago, :updated_at => 3.hours.ago)}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
22
+ lambda {@response_set.update_attributes(:api_id => "NEW")}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
23
+ lambda {@response_set.update_attributes(:access_code => "AND")}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
24
+ lambda {@response_set.update_attributes(:started_at => 10.days.ago)}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
25
+ lambda {@response_set.update_attributes(:completed_at => 2.hours.ago)}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
26
+ else
27
+ @response_set.attributes = {:created_at => 3.days.ago, :updated_at => 3.hours.ago} # automatically protected by Rails
28
+ @response_set.attributes = {:api_id => "NEW"} # Rails doesn't return false, but this will be checked in the comparison to saved_attrs
29
+ @response_set.attributes = {:access_code => "AND"}
30
+ @response_set.attributes = {:started_at => 10.days.ago}
31
+ @response_set.attributes = {:completed_at => 2.hours.ago}
32
+ end
33
+ @response_set.attributes.should == saved_attrs
34
+ end
35
+
36
+ describe '#access_code' do
37
+ let!(:rs1) { Factory(:response_set).tap { |rs| rs.update_attribute(:access_code, 'one') } }
38
+ let!(:rs2) { Factory(:response_set).tap { |rs| rs.update_attribute(:access_code, 'two') } }
39
+
40
+ # Regression test for #263
41
+ it 'accepts an access code in the constructor' do
42
+ rs = ResponseSet.new
43
+ rs.access_code = 'eleven'
44
+ rs.access_code.should == 'eleven'
45
+ end
46
+
47
+ # Regression test for #263
48
+ it 'setter accepts a conflicting access code' do
49
+ rs2.access_code = 'one'
50
+ rs2.access_code.should == 'one'
51
+ end
52
+
53
+ it 'is invalid when conflicting' do
54
+ rs2.access_code = 'one'
55
+ rs2.should_not be_valid
56
+ rs2.should have(1).errors_on(:access_code)
57
+ end
58
+ end
59
+
60
+ it "is completable" do
61
+ @response_set.completed_at.should be_nil
62
+ @response_set.complete!
63
+ @response_set.completed_at.should_not be_nil
64
+ @response_set.completed_at.is_a?(Time).should be_true
65
+ @response_set.should be_complete
66
+ end
67
+
68
+ it "does not allow completion through mass assignment" do
69
+ @response_set.completed_at.should be_nil
70
+ # Rails 3.2 throws an ActiveModel::MassAssignmentSecurity::Error error on response_set.update_attribues
71
+ # Using begin..rescue..end for Rails 3.1 and 3.0 backwards compatibility
72
+ # lambda { @response_set.update_attributes(:completed_at => Time.now) }.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
73
+ begin
74
+ @response_set.update_attributes(:completed_at => Time.now)
75
+ rescue
76
+ end
77
+ @response_set.completed_at.should be_nil
78
+ end
79
+
80
+ it 'saves its responses' do
81
+ new_set = ResponseSet.new(:survey => Factory(:survey))
82
+ new_set.responses.build(:question_id => 1, :answer_id => 1, :string_value => 'XXL')
83
+ new_set.save!
84
+
85
+ ResponseSet.find(new_set.id).responses.should have(1).items
86
+ end
87
+
88
+ describe '#update_from_ui_hash' do
89
+ let(:ui_hash) { {} }
90
+ let(:api_id) { 'ABCDEF-1234-567890' }
91
+
92
+ let(:question_id) { 42 }
93
+ let(:answer_id) { 137 }
94
+
95
+ def ui_response(attrs={})
96
+ { 'question_id' => question_id.to_s, 'api_id' => api_id }.merge(attrs)
97
+ end
98
+
99
+ def do_ui_update
100
+ response_set.update_from_ui_hash(ui_hash)
101
+ end
102
+
103
+ def resulting_response
104
+ # response_set_id criterion is to make sure a created response is
105
+ # appropriately associated.
106
+ Response.where(:api_id => api_id, :response_set_id => response_set).first
107
+ end
108
+
109
+ shared_examples 'pick one or any' do
110
+ it 'saves an answer alone' do
111
+ ui_hash['3'] = ui_response('answer_id' => set_answer_id)
112
+ do_ui_update
113
+ resulting_response.answer_id.should == answer_id
114
+ end
115
+
116
+ it 'preserves the question' do
117
+ ui_hash['4'] = ui_response('answer_id' => set_answer_id)
118
+ do_ui_update
119
+ resulting_response.question_id.should == question_id
120
+ end
121
+
122
+ it 'interprets a blank answer as no response' do
123
+ ui_hash['7'] = ui_response('answer_id' => blank_answer_id)
124
+ do_ui_update
125
+ resulting_response.should be_nil
126
+ end
127
+
128
+ it 'interprets no answer_id as no response' do
129
+ ui_hash['8'] = ui_response
130
+ do_ui_update
131
+ resulting_response.should be_nil
132
+ end
133
+
134
+ [
135
+ ['string_value', 'foo', '', 'foo'],
136
+ ['datetime_value', '2010-10-01 17:15', '', Time.zone.parse('2010-10-1 17:15')],
137
+ ['date_value', '2010-10-01', '', '2010-10-01'],
138
+ ['time_value', '17:15', '', '17:15'],
139
+ ['integer_value', '9', '', 9],
140
+ ['float_value', '4.0', '', 4.0],
141
+ ['text_value', 'more than foo', '', 'more than foo']
142
+ ].each do |value_type, set_value, blank_value, expected_value|
143
+ describe "plus #{value_type}" do
144
+ it 'saves the value' do
145
+ ui_hash['11'] = ui_response('answer_id' => set_answer_id, value_type => set_value)
146
+ do_ui_update
147
+ resulting_response.send(value_type).should == expected_value
148
+ end
149
+
150
+ it 'interprets a blank answer as no response' do
151
+ ui_hash['18'] = ui_response('answer_id' => blank_answer_id, value_type => set_value)
152
+ do_ui_update
153
+ resulting_response.should be_nil
154
+ end
155
+
156
+ it 'interprets a blank value as no response' do
157
+ ui_hash['29'] = ui_response('answer_id' => set_answer_id, value_type => blank_value)
158
+ do_ui_update
159
+ resulting_response.should be_nil
160
+ end
161
+
162
+ it 'interprets no answer_id as no response' do
163
+ ui_hash['8'] = ui_response(value_type => set_value)
164
+ do_ui_update
165
+ resulting_response.should be_nil
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ shared_examples 'response interpretation' do
172
+ it 'fails when api_id is not provided' do
173
+ ui_hash['0'] = { 'question_id' => question_id }
174
+ lambda { do_ui_update }.should raise_error(/api_id missing from response 0/)
175
+ end
176
+
177
+ describe 'for a radio button' do
178
+ let(:set_answer_id) { answer_id.to_s }
179
+ let(:blank_answer_id) { '' }
180
+
181
+ include_examples 'pick one or any'
182
+ end
183
+
184
+ describe 'for a checkbox' do
185
+ let(:set_answer_id) { ['', answer_id.to_s] }
186
+ let(:blank_answer_id) { [''] }
187
+
188
+ include_examples 'pick one or any'
189
+ end
190
+ end
191
+
192
+ describe 'with a new response' do
193
+ include_examples 'response interpretation'
194
+
195
+ # After much effort I cannot produce this situation in a test, either with
196
+ # with threads or separate processes. While SQLite 3 will nominally allow
197
+ # for some coarse-grained concurrency, it does not appear to work with
198
+ # simultaneous write transactions the way AR uses SQLite. Instead,
199
+ # simultaneous write transactions always result in a
200
+ # SQLite3::BusyException, regardless of the connection's timeout setting.
201
+ it 'fails predicably when another response with the same api_id is created in a simultaneous open transaction'
202
+ end
203
+
204
+ describe 'with an existing response' do
205
+ let!(:original_response) {
206
+ response_set.responses.build(:question_id => question_id, :answer_id => answer_id).tap do |r|
207
+ r.api_id = api_id # not mass assignable
208
+ r.save!
209
+ end
210
+ }
211
+
212
+ include_examples 'response interpretation'
213
+
214
+ it 'fails when the existing response is for a different question' do
215
+ ui_hash['76'] = ui_response('question_id' => '43', 'answer_id' => answer_id.to_s)
216
+
217
+ lambda { do_ui_update }.should raise_error(/Illegal attempt to change question for response #{api_id}./)
218
+ end
219
+ end
220
+
221
+ # clean_with_truncation is necessary because AR 3.0 can't roll back a nested
222
+ # transaction with SQLite.
223
+ it 'rolls back all changes on failure', :clean_with_truncation do
224
+ ui_hash['0'] = ui_response('question_id' => '42', 'answer_id' => answer_id.to_s)
225
+ ui_hash['1'] = { 'answer_id' => '7' } # no api_id
226
+
227
+ begin
228
+ do_ui_update
229
+ fail "Expected error did not occur"
230
+ rescue
231
+ end
232
+
233
+ response_set.reload.responses.should be_empty
234
+ end
235
+ end
236
+ end
237
+
238
+ describe ResponseSet, "with dependencies" do
239
+ before(:each) do
240
+ @section = Factory(:survey_section)
241
+ # Questions
242
+ @do_you_like_pie = Factory(:question, :text => "Do you like pie?", :survey_section => @section)
243
+ @what_flavor = Factory(:question, :text => "What flavor?", :survey_section => @section)
244
+ @what_bakery = Factory(:question, :text => "What bakery?", :survey_section => @section)
245
+ # Answers
246
+ @do_you_like_pie.answers << Factory(:answer, :text => "yes", :question_id => @do_you_like_pie.id)
247
+ @do_you_like_pie.answers << Factory(:answer, :text => "no", :question_id => @do_you_like_pie.id)
248
+ @what_flavor.answers << Factory(:answer, :response_class => :string, :question_id => @what_flavor.id)
249
+ @what_bakery.answers << Factory(:answer, :response_class => :string, :question_id => @what_bakery.id)
250
+ # Dependency
251
+ @what_flavor_dep = Factory(:dependency, :rule => "A", :question_id => @what_flavor.id)
252
+ Factory(: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)
253
+ @what_bakery_dep = Factory(:dependency, :rule => "B", :question_id => @what_bakery.id)
254
+ Factory(: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)
255
+ # Responses
256
+ @response_set = Factory(:response_set)
257
+ @response_set.responses << Factory(:response, :question_id => @do_you_like_pie.id, :answer_id => @do_you_like_pie.answers.first.id, :response_set_id => @response_set.id)
258
+ @response_set.responses << Factory(:response, :string_value => "pecan pie", :question_id => @what_flavor.id, :answer_id => @what_flavor.answers.first.id, :response_set_id => @response_set.id)
259
+ end
260
+
261
+ it "should list unanswered dependencies to show at the top of the next page (javascript turned off)" do
262
+ @response_set.unanswered_dependencies.should == [@what_bakery]
263
+ end
264
+ it "should list answered and unanswered dependencies to show inline (javascript turned on)" do
265
+ @response_set.all_dependencies[:show].should == ["q_#{@what_flavor.id}", "q_#{@what_bakery.id}"]
266
+ end
267
+ it "should list group as dependency" do
268
+ # Question Group
269
+ crust_group = Factory(:question_group, :text => "Favorite Crusts")
270
+
271
+ # Question
272
+ what_crust = Factory(:question, :text => "What is your favorite curst type?", :survey_section => @section)
273
+ crust_group.questions << what_crust
274
+
275
+ # Answers
276
+ what_crust.answers << Factory(:answer, :response_class => :string, :question_id => what_crust.id)
277
+
278
+ # Dependency
279
+ crust_group_dep = Factory(:dependency, :rule => "C", :question_group_id => crust_group.id, :question => nil)
280
+ Factory(: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)
281
+
282
+ @response_set.unanswered_dependencies.should == [@what_bakery, crust_group]
283
+ end
284
+ end
285
+ describe ResponseSet, "dependency_conditions" do
286
+ before do
287
+ @section = Factory(:survey_section)
288
+ # Questions
289
+ @like_pie = Factory(:question, :text => "Do you like pie?", :survey_section => @section)
290
+ @like_jam = Factory(:question, :text => "Do you like jam?", :survey_section => @section)
291
+ @what_is_wrong_with_you = Factory(:question, :text => "What's wrong with you?", :survey_section => @section)
292
+ # Answers
293
+ @like_pie.answers << Factory(:answer, :text => "yes", :question_id => @like_pie.id)
294
+ @like_pie.answers << Factory(:answer, :text => "no", :question_id => @like_pie.id)
295
+ @like_jam.answers << Factory(:answer, :text => "yes", :question_id => @like_jam.id)
296
+ @like_jam.answers << Factory(:answer, :text => "no", :question_id => @like_jam.id)
297
+ # Dependency
298
+ @what_is_wrong_with_you = Factory(:dependency, :rule => "A or B", :question_id => @what_is_wrong_with_you.id)
299
+ @dep_a = Factory(: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)
300
+ @dep_b = Factory(: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)
301
+ # Responses
302
+ @response_set = Factory(:response_set)
303
+ @response_set.responses << Factory(:response, :question_id => @like_pie.id, :answer_id => @like_pie.answers.last.id, :response_set_id => @response_set.id)
304
+ end
305
+ it "should list all dependencies for answered questions" do
306
+ dependency_conditions = @response_set.send(:dependencies).last.dependency_conditions
307
+ dependency_conditions.size.should == 2
308
+ dependency_conditions.should include(@dep_a)
309
+ dependency_conditions.should include(@dep_b)
310
+
311
+ end
312
+ it "should list all dependencies for passed question_id" do
313
+ # Questions
314
+ like_ice_cream = Factory(:question, :text => "Do you like ice_cream?", :survey_section => @section)
315
+ what_flavor = Factory(:question, :text => "What flavor?", :survey_section => @section)
316
+ # Answers
317
+ like_ice_cream.answers << Factory(:answer, :text => "yes", :question_id => like_ice_cream.id)
318
+ like_ice_cream.answers << Factory(:answer, :text => "no", :question_id => like_ice_cream.id)
319
+ what_flavor.answers << Factory(:answer, :response_class => :string, :question_id => what_flavor.id)
320
+ # Dependency
321
+ flavor_dependency = Factory(:dependency, :rule => "C", :question_id => what_flavor.id)
322
+ flavor_dependency_condition = Factory(:dependency_condition, :rule_key => "A", :question_id => like_ice_cream.id, :operator => "==",
323
+ :answer_id => like_ice_cream.answers.first.id, :dependency_id => flavor_dependency.id)
324
+ # Responses
325
+ dependency_conditions = @response_set.send(:dependencies, like_ice_cream.id).should == [flavor_dependency]
326
+ end
327
+ end
328
+
329
+ describe ResponseSet, "as a quiz" do
330
+ before(:each) do
331
+ @survey = Factory(:survey)
332
+ @section = Factory(:survey_section, :survey => @survey)
333
+ @response_set = Factory(:response_set, :survey => @survey)
334
+ end
335
+ def generate_responses(count, quiz = nil, correct = nil)
336
+ count.times do |i|
337
+ q = Factory(:question, :survey_section => @section)
338
+ a = Factory(:answer, :question => q, :response_class => "answer")
339
+ x = Factory(:answer, :question => q, :response_class => "answer")
340
+ q.correct_answer = (quiz == "quiz" ? a : nil)
341
+ @response_set.responses << Factory(:response, :question => q, :answer => (correct == "correct" ? a : x))
342
+ end
343
+ end
344
+
345
+ it "should report correctness if it is a quiz" do
346
+ generate_responses(3, "quiz", "correct")
347
+ @response_set.correct?.should be_true
348
+ @response_set.correctness_hash.should == {:questions => 3, :responses => 3, :correct => 3}
349
+ end
350
+ it "should report incorrectness if it is a quiz" do
351
+ generate_responses(3, "quiz", "incorrect")
352
+ @response_set.correct?.should be_false
353
+ @response_set.correctness_hash.should == {:questions => 3, :responses => 3, :correct => 0}
354
+ end
355
+ it "should report correct if it isn't a quiz" do
356
+ generate_responses(3, "non-quiz")
357
+ @response_set.correct?.should be_true
358
+ @response_set.correctness_hash.should == {:questions => 3, :responses => 3, :correct => 3}
359
+ end
360
+ end
361
+ describe ResponseSet, "with mandatory questions" do
362
+ before(:each) do
363
+ @survey = Factory(:survey)
364
+ @section = Factory(:survey_section, :survey => @survey)
365
+ @response_set = Factory(:response_set, :survey => @survey)
366
+ end
367
+ def generate_responses(count, mandatory = nil, responded = nil)
368
+ count.times do |i|
369
+ q = Factory(:question, :survey_section => @section, :is_mandatory => (mandatory == "mandatory"))
370
+ a = Factory(:answer, :question => q, :response_class => "answer")
371
+ if responded == "responded"
372
+ @response_set.responses << Factory(:response, :question => q, :answer => a)
373
+ end
374
+ end
375
+ end
376
+ it "should report progress without mandatory questions" do
377
+ generate_responses(3)
378
+ @response_set.mandatory_questions_complete?.should be_true
379
+ @response_set.progress_hash.should == {:questions => 3, :triggered => 3, :triggered_mandatory => 0, :triggered_mandatory_completed => 0}
380
+ end
381
+ it "should report progress with mandatory questions" do
382
+ generate_responses(3, "mandatory", "responded")
383
+ @response_set.mandatory_questions_complete?.should be_true
384
+ @response_set.progress_hash.should == {:questions => 3, :triggered => 3, :triggered_mandatory => 3, :triggered_mandatory_completed => 3}
385
+ end
386
+ it "should report progress with mandatory questions" do
387
+ generate_responses(3, "mandatory", "not-responded")
388
+ @response_set.mandatory_questions_complete?.should be_false
389
+ @response_set.progress_hash.should == {:questions => 3, :triggered => 3, :triggered_mandatory => 3, :triggered_mandatory_completed => 0}
390
+ end
391
+ it "should ignore labels and images" do
392
+ generate_responses(3, "mandatory", "responded")
393
+ Factory(:question, :survey_section => @section, :display_type => "label", :is_mandatory => true)
394
+ Factory(:question, :survey_section => @section, :display_type => "image", :is_mandatory => true)
395
+ @response_set.mandatory_questions_complete?.should be_true
396
+ @response_set.progress_hash.should == {:questions => 5, :triggered => 5, :triggered_mandatory => 5, :triggered_mandatory_completed => 5}
397
+ end
398
+ end
399
+ describe ResponseSet, "with mandatory, dependent questions" do
400
+ before(:each) do
401
+ @survey = Factory(:survey)
402
+ @section = Factory(:survey_section, :survey => @survey)
403
+ @response_set = Factory(:response_set, :survey => @survey)
404
+ end
405
+ def generate_responses(count, mandatory = nil, dependent = nil, triggered = nil)
406
+ dq = Factory(:question, :survey_section => @section, :is_mandatory => (mandatory == "mandatory"))
407
+ da = Factory(:answer, :question => dq, :response_class => "answer")
408
+ dx = Factory(:answer, :question => dq, :response_class => "answer")
409
+ count.times do |i|
410
+ q = Factory(:question, :survey_section => @section, :is_mandatory => (mandatory == "mandatory"))
411
+ a = Factory(:answer, :question => q, :response_class => "answer")
412
+ if dependent == "dependent"
413
+ d = Factory(:dependency, :question => q)
414
+ dc = Factory(:dependency_condition, :dependency => d, :question_id => dq.id, :answer_id => da.id)
415
+ end
416
+ @response_set.responses << Factory(:response, :response_set => @response_set, :question => dq, :answer => (triggered == "triggered" ? da : dx))
417
+ @response_set.responses << Factory(:response, :response_set => @response_set, :question => q, :answer => a)
418
+ end
419
+ end
420
+ it "should report progress without mandatory questions" do
421
+ generate_responses(3, "mandatory", "dependent")
422
+ @response_set.mandatory_questions_complete?.should be_true
423
+ @response_set.progress_hash.should == {:questions => 4, :triggered => 1, :triggered_mandatory => 1, :triggered_mandatory_completed => 1}
424
+ end
425
+ it "should report progress with mandatory questions" do
426
+ generate_responses(3, "mandatory", "dependent", "triggered")
427
+ @response_set.mandatory_questions_complete?.should be_true
428
+ @response_set.progress_hash.should == {:questions => 4, :triggered => 4, :triggered_mandatory => 4, :triggered_mandatory_completed => 4}
429
+ end
430
+ end
431
+ describe ResponseSet, "exporting csv" do
432
+ before(:each) do
433
+ @section = Factory(:survey_section)
434
+ # Questions
435
+ @do_you_like_pie = Factory(:question, :text => "Do you like pie?", :survey_section => @section)
436
+ @what_flavor = Factory(:question, :text => "What flavor?", :survey_section => @section)
437
+ @what_bakery = Factory(:question, :text => "What bakery?", :survey_section => @section)
438
+ # Answers
439
+ @do_you_like_pie.answers << Factory(:answer, :text => "yes", :question_id => @do_you_like_pie.id)
440
+ @do_you_like_pie.answers << Factory(:answer, :text => "no", :question_id => @do_you_like_pie.id)
441
+ @what_flavor.answers << Factory(:answer, :response_class => :string, :question_id => @what_flavor.id)
442
+ @what_bakery.answers << Factory(:answer, :response_class => :string, :question_id => @what_bakery.id)
443
+ # Responses
444
+ @response_set = Factory(:response_set)
445
+ @response_set.responses << Factory(:response, :question_id => @do_you_like_pie.id, :answer_id => @do_you_like_pie.answers.first.id, :response_set_id => @response_set.id)
446
+ @response_set.responses << Factory(:response, :string_value => "pecan pie", :question_id => @what_flavor.id, :answer_id => @what_flavor.answers.first.id, :response_set_id => @response_set.id)
447
+ end
448
+ it "should export a string with responses" do
449
+ @response_set.responses.size.should == 2
450
+ csv = @response_set.to_csv
451
+ csv.is_a?(String).should be_true
452
+ csv.should match "question.short_text"
453
+ csv.should match "What flavor?"
454
+ csv.should match /pecan pie/
455
+ end
456
+ end
457
+
458
+ describe ResponseSet, "#as_json" do
459
+ let(:rs) {
460
+ Factory(:response_set, :responses => [
461
+ Factory(:response, :question => Factory(:question), :answer => Factory(:answer), :string_value => '2')])
462
+ }
463
+
464
+ let(:js) {rs.as_json}
465
+
466
+ it "should include uuid, survey_id" do
467
+ js[:uuid].should == rs.api_id
468
+ end
469
+
470
+ it "should include responses with uuid, question_id, answer_id, value" do
471
+ r0 = rs.responses[0]
472
+ js[:responses][0][:uuid].should == r0.api_id
473
+ js[:responses][0][:answer_id].should == r0.answer.api_id
474
+ js[:responses][0][:question_id].should == r0.question.api_id
475
+ js[:responses][0][:value].should == r0.string_value
476
+ end
477
+ end