surveyor 0.22.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +83 -0
- data/Gemfile.rails_version +7 -1
- data/README.md +114 -16
- data/Rakefile +15 -9
- data/app/inputs/quiet_input.rb +5 -0
- data/app/inputs/surveyor_check_boxes_input.rb +35 -0
- data/app/inputs/surveyor_radio_input.rb +18 -0
- data/app/views/partials/_answer.html.haml +4 -4
- data/app/views/partials/_question.html.haml +7 -7
- data/app/views/partials/_question_group.html.haml +9 -8
- data/app/views/surveyor/export.json.rabl +28 -25
- data/app/views/surveyor/new.html.haml +8 -5
- data/app/views/surveyor/show.json.rabl +3 -2
- data/ci-exec.sh +13 -7
- data/cucumber.yml +3 -3
- data/doc/REPRESENTATIONS.md +34 -0
- data/doc/api_id_schema.json +7 -0
- data/doc/response_set_schema.json +54 -0
- data/doc/surveyor question combinations.png +0 -0
- data/doc/surveyor_timestamp_schema.json +9 -0
- data/features/ajax_submissions.feature +140 -0
- data/features/export_to_json.feature +182 -34
- data/features/no_duplicates.feature +110 -0
- data/features/show_survey.feature +1 -1
- data/features/step_definitions/parser_steps.rb +25 -2
- data/features/step_definitions/surveyor_steps.rb +145 -20
- data/features/step_definitions/ui_steps.rb +3 -0
- data/features/support/database_cleaner.rb +16 -0
- data/features/support/env.rb +21 -17
- data/features/support/simultaneous_ajax.rb +101 -0
- data/features/support/single_quit_selenium_driver.rb +23 -0
- data/features/support/slow_updates.rb +18 -0
- data/features/surveyor.feature +174 -44
- data/features/surveyor_dependencies.feature +80 -39
- data/features/surveyor_parser.feature +114 -20
- data/features/z_redcap_parser.feature +0 -1
- data/lib/{generators/surveyor/templates/public → assets}/images/surveyor/next.gif +0 -0
- data/lib/{generators/surveyor/templates/public → assets}/images/surveyor/prev.gif +0 -0
- data/lib/{generators/surveyor/templates/public/stylesheets/surveyor → assets/images}/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/lib/{generators/surveyor/templates/public/stylesheets/surveyor → assets/images}/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/lib/{generators/surveyor/templates/public/stylesheets/surveyor → assets/images}/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/lib/{generators/surveyor/templates/public/stylesheets/surveyor → assets/images}/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/lib/assets/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/lib/{generators/surveyor/templates/public/stylesheets/surveyor → assets/images}/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/lib/{generators/surveyor/templates/public/stylesheets/surveyor → assets/images}/ui-icons_ef8c08_256x240.png +0 -0
- data/lib/{generators/surveyor/templates/public/stylesheets/surveyor → assets/images}/ui-icons_ffffff_256x240.png +0 -0
- data/lib/{generators/surveyor/templates/public → assets}/javascripts/surveyor/jquery-ui-timepicker-addon.js +23 -23
- data/lib/{generators/surveyor/templates/public → assets}/javascripts/surveyor/jquery-ui.js +125 -125
- data/lib/assets/javascripts/surveyor/jquery.selectToUISlider.js +240 -0
- data/lib/{generators/surveyor/templates/public → assets}/javascripts/surveyor/jquery.surveyor.js +52 -57
- data/lib/{generators/surveyor/templates/public → assets}/javascripts/surveyor/jquery.tools.min.js +7 -7
- data/lib/{generators/surveyor/templates/public → assets}/stylesheets/surveyor/dateinput.css +13 -13
- data/lib/{generators/surveyor/templates/public → assets}/stylesheets/surveyor/jquery-ui-timepicker-addon.css +0 -0
- data/lib/{generators/surveyor/templates/public → assets}/stylesheets/surveyor/jquery-ui.custom.css +17 -17
- data/lib/{generators/surveyor/templates/public → assets}/stylesheets/surveyor/reset.css +1 -1
- data/lib/{generators/surveyor/templates/public → assets}/stylesheets/surveyor/results.css +0 -0
- data/lib/assets/stylesheets/surveyor/ui.slider.extras.css +110 -0
- data/lib/{generators/surveyor/templates/public/stylesheets/sass → assets/stylesheets}/surveyor.sass +15 -7
- data/lib/generators/surveyor/custom_generator.rb +3 -2
- data/lib/generators/surveyor/install_generator.rb +59 -17
- data/lib/generators/surveyor/templates/app/assets/javascripts/surveyor_all.js +5 -0
- data/lib/generators/surveyor/templates/app/assets/stylesheets/surveyor_all.css +9 -0
- data/lib/generators/surveyor/templates/app/controllers/surveyor_controller.rb +2 -1
- data/lib/generators/surveyor/templates/app/views/layouts/surveyor_custom.html.erb +1 -0
- data/lib/generators/surveyor/templates/config/locales/surveyor_es.yml +1 -0
- data/lib/generators/surveyor/templates/config/locales/surveyor_he.yml +1 -0
- data/lib/generators/surveyor/templates/db/migrate/add_api_id_to_question_groups.rb +1 -0
- data/lib/generators/surveyor/templates/db/migrate/add_api_ids.rb +1 -0
- data/lib/generators/surveyor/templates/db/migrate/add_api_ids_to_response_sets_and_responses.rb +1 -0
- data/lib/generators/surveyor/templates/db/migrate/add_correct_answer_id_to_questions.rb +1 -0
- data/lib/generators/surveyor/templates/db/migrate/add_default_value_to_answers.rb +1 -0
- data/lib/generators/surveyor/templates/db/migrate/add_display_order_to_surveys.rb +1 -0
- data/lib/generators/surveyor/templates/db/migrate/add_display_type_to_answers.rb +1 -0
- data/lib/generators/surveyor/templates/db/migrate/add_index_to_response_sets.rb +1 -0
- data/lib/generators/surveyor/templates/db/migrate/add_index_to_surveys.rb +1 -0
- data/lib/generators/surveyor/templates/db/migrate/add_section_id_to_responses.rb +1 -0
- data/lib/generators/surveyor/templates/db/migrate/add_unique_index_on_access_code_and_version_in_surveys.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/add_unique_indicies.rb +3 -2
- data/lib/generators/surveyor/templates/db/migrate/add_version_to_surveys.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/api_ids_must_be_unique.rb +23 -0
- data/lib/generators/surveyor/templates/db/migrate/create_answers.rb +6 -5
- data/lib/generators/surveyor/templates/db/migrate/create_dependencies.rb +2 -1
- data/lib/generators/surveyor/templates/db/migrate/create_dependency_conditions.rb +2 -1
- data/lib/generators/surveyor/templates/db/migrate/create_question_groups.rb +5 -4
- data/lib/generators/surveyor/templates/db/migrate/create_questions.rb +4 -3
- data/lib/generators/surveyor/templates/db/migrate/create_response_sets.rb +1 -0
- data/lib/generators/surveyor/templates/db/migrate/create_responses.rb +10 -9
- data/lib/generators/surveyor/templates/db/migrate/create_survey_sections.rb +5 -4
- data/lib/generators/surveyor/templates/db/migrate/create_surveys.rb +4 -3
- data/lib/generators/surveyor/templates/db/migrate/create_validation_conditions.rb +5 -4
- data/lib/generators/surveyor/templates/db/migrate/create_validations.rb +3 -2
- data/lib/generators/surveyor/templates/db/migrate/drop_unique_index_on_access_code_in_surveys.rb +10 -0
- data/lib/generators/surveyor/templates/db/migrate/update_blank_api_ids_on_question_group.rb +22 -0
- data/lib/generators/surveyor/templates/db/migrate/update_blank_versions_on_surveys.rb +13 -0
- data/lib/generators/surveyor/templates/surveys/date_survey.rb +1 -0
- data/lib/generators/surveyor/templates/surveys/kitchen_sink_survey.rb +54 -24
- data/lib/generators/surveyor/templates/surveys/quiz.rb +1 -0
- data/lib/generators/surveyor/templates/{public/stylesheets/sass → vendor/assets/stylesheets}/custom.sass +1 -1
- data/lib/surveyor/common.rb +16 -31
- data/lib/surveyor/engine.rb +2 -4
- data/lib/surveyor/helpers/asset_pipeline.rb +13 -0
- data/lib/surveyor/helpers/formtastic_custom_input.rb +17 -0
- data/lib/surveyor/helpers/surveyor_helper_methods.rb +10 -8
- data/lib/surveyor/models/answer_methods.rb +3 -0
- data/lib/surveyor/models/dependency_condition_methods.rb +27 -28
- data/lib/surveyor/models/dependency_methods.rb +3 -0
- data/lib/surveyor/models/question_group_methods.rb +3 -0
- data/lib/surveyor/models/question_methods.rb +10 -7
- data/lib/surveyor/models/response_methods.rb +16 -0
- data/lib/surveyor/models/response_set_methods.rb +71 -64
- data/lib/surveyor/models/survey_methods.rb +19 -28
- data/lib/surveyor/models/survey_section_methods.rb +3 -0
- data/lib/surveyor/models/validation_condition_methods.rb +4 -2
- data/lib/surveyor/models/validation_methods.rb +3 -0
- data/lib/surveyor/parser.rb +198 -148
- data/lib/surveyor/redcap_parser.rb +120 -80
- data/lib/surveyor/surveyor_controller_methods.rb +86 -37
- data/lib/surveyor/version.rb +2 -2
- data/lib/surveyor.rb +5 -6
- data/lib/tasks/surveyor_tasks.rake +19 -7
- data/spec/controllers/surveyor_controller_spec.rb +166 -92
- data/spec/factories.rb +33 -32
- data/spec/helpers/formtastic_custom_input_spec.rb +16 -0
- data/spec/lib/common_spec.rb +0 -39
- data/spec/lib/redcap_parser_spec.rb +24 -24
- data/spec/models/answer_spec.rb +12 -0
- data/spec/models/dependency_condition_spec.rb +279 -323
- data/spec/models/dependency_spec.rb +10 -0
- data/spec/models/question_group_spec.rb +12 -0
- data/spec/models/question_spec.rb +12 -0
- data/spec/models/response_set_spec.rb +189 -139
- data/spec/models/response_spec.rb +60 -0
- data/spec/models/survey_section_spec.rb +9 -0
- data/spec/models/survey_spec.rb +72 -9
- data/spec/models/validation_condition_spec.rb +9 -1
- data/spec/models/validation_spec.rb +10 -0
- data/spec/spec_helper.rb +25 -6
- data/surveyor.gemspec +5 -4
- metadata +332 -291
- data/features/step_definitions/common_steps.rb +0 -3
- data/lib/formtastic/surveyor_builder.rb +0 -82
- data/lib/generators/surveyor/templates/public/javascripts/surveyor/jquery.blockUI.js +0 -499
@@ -43,6 +43,16 @@ describe Dependency do
|
|
43
43
|
@dependency.rule = "a and b"
|
44
44
|
@dependency.should have(1).error_on(:rule)
|
45
45
|
end
|
46
|
+
it "should protect timestamps" do
|
47
|
+
saved_attrs = @dependency.attributes
|
48
|
+
if defined? ActiveModel::MassAssignmentSecurity::Error
|
49
|
+
lambda {@dependency.update_attributes(:created_at => 3.days.ago, :updated_at => 3.hours.ago)}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
50
|
+
else
|
51
|
+
@dependency.attributes = {:created_at => 3.days.ago, :updated_at => 3.hours.ago} # automatically protected by Rails
|
52
|
+
end
|
53
|
+
@dependency.attributes.should == saved_attrs
|
54
|
+
end
|
55
|
+
|
46
56
|
|
47
57
|
end
|
48
58
|
|
@@ -32,4 +32,16 @@ describe QuestionGroup do
|
|
32
32
|
@dependency.should_receive(:is_met?).and_return(false)
|
33
33
|
@question_group.css_class(Factory(:response_set)).should == "g_dependent g_hidden foo bar"
|
34
34
|
end
|
35
|
+
it "should protect api_id, timestamps" do
|
36
|
+
saved_attrs = @question_group.attributes
|
37
|
+
if defined? ActiveModel::MassAssignmentSecurity::Error
|
38
|
+
lambda {@question_group.update_attributes(:created_at => 3.days.ago, :updated_at => 3.hours.ago)}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
39
|
+
lambda {@question_group.update_attributes(:api_id => "NEW")}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
40
|
+
else
|
41
|
+
@question_group.attributes = {:created_at => 3.days.ago, :updated_at => 3.hours.ago} # automatically protected by Rails
|
42
|
+
@question_group.attributes = {:api_id => "NEW"} # Rails doesn't return false, but this will be checked in the comparison to saved_attrs
|
43
|
+
end
|
44
|
+
@question_group.attributes.should == saved_attrs
|
45
|
+
end
|
46
|
+
|
35
47
|
end
|
@@ -42,6 +42,18 @@ describe Question, "when creating a new question" do
|
|
42
42
|
it "should have an api_id" do
|
43
43
|
@question.api_id.length.should == 36
|
44
44
|
end
|
45
|
+
|
46
|
+
it "should protect api_id, timestamps" do
|
47
|
+
saved_attrs = @question.attributes
|
48
|
+
if defined? ActiveModel::MassAssignmentSecurity::Error
|
49
|
+
lambda {@question.update_attributes(:created_at => 3.days.ago, :updated_at => 3.hours.ago)}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
50
|
+
lambda {@question.update_attributes(:api_id => "NEW")}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
51
|
+
else
|
52
|
+
@question.attributes = {:created_at => 3.days.ago, :updated_at => 3.hours.ago} # automatically protected by Rails
|
53
|
+
@question.attributes = {:api_id => "NEW"} # Rails doesn't return false, but this will be checked in the comparison to saved_attrs
|
54
|
+
end
|
55
|
+
@question.attributes.should == saved_attrs
|
56
|
+
end
|
45
57
|
end
|
46
58
|
|
47
59
|
describe Question, "that has answers" do
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
2
|
|
3
3
|
describe ResponseSet do
|
4
|
+
let(:response_set) { Factory(:response_set) }
|
5
|
+
|
4
6
|
before(:each) do
|
5
7
|
@response_set = Factory(:response_set)
|
6
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"}})
|
@@ -13,13 +15,33 @@ describe ResponseSet do
|
|
13
15
|
@response_set.access_code.length.should == 10
|
14
16
|
end
|
15
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
|
+
|
16
36
|
describe '#access_code' do
|
17
37
|
let!(:rs1) { Factory(:response_set).tap { |rs| rs.update_attribute(:access_code, 'one') } }
|
18
38
|
let!(:rs2) { Factory(:response_set).tap { |rs| rs.update_attribute(:access_code, 'two') } }
|
19
39
|
|
20
40
|
# Regression test for #263
|
21
41
|
it 'accepts an access code in the constructor' do
|
22
|
-
ResponseSet.new
|
42
|
+
rs = ResponseSet.new
|
43
|
+
rs.access_code = 'eleven'
|
44
|
+
rs.access_code.should == 'eleven'
|
23
45
|
end
|
24
46
|
|
25
47
|
# Regression test for #263
|
@@ -33,14 +55,6 @@ describe ResponseSet do
|
|
33
55
|
rs2.should_not be_valid
|
34
56
|
rs2.should have(1).errors_on(:access_code)
|
35
57
|
end
|
36
|
-
|
37
|
-
it 'defaults to a random, non-conflicting value on init' do
|
38
|
-
Surveyor::Common.should_receive(:make_tiny_code).and_return('one')
|
39
|
-
Surveyor::Common.should_receive(:make_tiny_code).and_return('two')
|
40
|
-
Surveyor::Common.should_receive(:make_tiny_code).and_return('three')
|
41
|
-
|
42
|
-
ResponseSet.new.access_code.should == 'three'
|
43
|
-
end
|
44
58
|
end
|
45
59
|
|
46
60
|
it "is completable" do
|
@@ -63,18 +77,6 @@ describe ResponseSet do
|
|
63
77
|
@response_set.completed_at.should be_nil
|
64
78
|
end
|
65
79
|
|
66
|
-
it "should save new responses from radio buttons, ignoring blanks" do
|
67
|
-
@response_set.update_attributes(:responses_attributes => ResponseSet.to_savable(@radio_response_attributes))
|
68
|
-
@response_set.responses.should have(2).items
|
69
|
-
@response_set.responses.detect{|r| r.question_id == 2}.answer_id.should == 6
|
70
|
-
end
|
71
|
-
|
72
|
-
it "should save new responses from other types, ignoring blanks" do
|
73
|
-
@response_set.update_attributes(:responses_attributes => ResponseSet.to_savable(@other_response_attributes))
|
74
|
-
@response_set.responses.should have(1).items
|
75
|
-
@response_set.responses.detect{|r| r.question_id == 7}.text_value.should == "Brian is tired"
|
76
|
-
end
|
77
|
-
|
78
80
|
it 'saves its responses' do
|
79
81
|
new_set = ResponseSet.new(:survey => Factory(:survey))
|
80
82
|
new_set.responses.build(:question_id => 1, :answer_id => 1, :string_value => 'XXL')
|
@@ -83,124 +85,151 @@ describe ResponseSet do
|
|
83
85
|
ResponseSet.find(new_set.id).responses.should have(1).items
|
84
86
|
end
|
85
87
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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', '', Date.new(2010, 10, 1)],
|
137
|
+
['integer_value', '9', '', 9],
|
138
|
+
['float_value', '4.0', '', 4.0],
|
139
|
+
['text_value', 'more than foo', '', 'more than foo']
|
140
|
+
].each do |value_type, set_value, blank_value, expected_value|
|
141
|
+
describe "plus #{value_type}" do
|
142
|
+
it 'saves the value' do
|
143
|
+
ui_hash['11'] = ui_response('answer_id' => set_answer_id, value_type => set_value)
|
144
|
+
do_ui_update
|
145
|
+
resulting_response.send(value_type).should == expected_value
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'interprets a blank answer as no response' do
|
149
|
+
ui_hash['18'] = ui_response('answer_id' => blank_answer_id, value_type => set_value)
|
150
|
+
do_ui_update
|
151
|
+
resulting_response.should be_nil
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'interprets a blank value as no response' do
|
155
|
+
ui_hash['29'] = ui_response('answer_id' => set_answer_id, value_type => blank_value)
|
156
|
+
do_ui_update
|
157
|
+
resulting_response.should be_nil
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'interprets no answer_id as no response' do
|
161
|
+
ui_hash['8'] = ui_response(value_type => set_value)
|
162
|
+
do_ui_update
|
163
|
+
resulting_response.should be_nil
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
shared_examples 'response interpretation' do
|
170
|
+
it 'fails when api_id is not provided' do
|
171
|
+
ui_hash['0'] = { 'question_id' => question_id }
|
172
|
+
lambda { do_ui_update }.should raise_error(/api_id missing from response 0/)
|
173
|
+
end
|
174
|
+
|
175
|
+
describe 'for a radio button' do
|
176
|
+
let(:set_answer_id) { answer_id.to_s }
|
177
|
+
let(:blank_answer_id) { '' }
|
178
|
+
|
179
|
+
include_examples 'pick one or any'
|
180
|
+
end
|
181
|
+
|
182
|
+
describe 'for a checkbox' do
|
183
|
+
let(:set_answer_id) { ['', answer_id.to_s] }
|
184
|
+
let(:blank_answer_id) { [''] }
|
185
|
+
|
186
|
+
include_examples 'pick one or any'
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe 'with a new response' do
|
191
|
+
include_examples 'response interpretation'
|
192
|
+
|
193
|
+
# After much effort I cannot produce this situation in a test, either with
|
194
|
+
# with threads or separate processes. While SQLite 3 will nominally allow
|
195
|
+
# for some coarse-grained concurrency, it does not appear to work with
|
196
|
+
# simultaneous write transactions the way AR uses SQLite. Instead,
|
197
|
+
# simultaneous write transactions always result in a
|
198
|
+
# SQLite3::BusyException, regardless of the connection's timeout setting.
|
199
|
+
it 'fails predicably when another response with the same api_id is created in a simultaneous open transaction'
|
200
|
+
end
|
201
|
+
|
202
|
+
describe 'with an existing response' do
|
203
|
+
let!(:original_response) {
|
204
|
+
response_set.responses.build(:question_id => question_id, :answer_id => answer_id).tap do |r|
|
205
|
+
r.api_id = api_id # not mass assignable
|
206
|
+
r.save!
|
207
|
+
end
|
208
|
+
}
|
209
|
+
|
210
|
+
include_examples 'response interpretation'
|
211
|
+
|
212
|
+
it 'fails when the existing response is for a different question' do
|
213
|
+
ui_hash['76'] = ui_response('question_id' => '43', 'answer_id' => answer_id.to_s)
|
214
|
+
|
215
|
+
lambda { do_ui_update }.should raise_error(/Illegal attempt to change question for response #{api_id}./)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# clean_with_truncation is necessary because AR 3.0 can't roll back a nested
|
220
|
+
# transaction with SQLite.
|
221
|
+
it 'rolls back all changes on failure', :clean_with_truncation do
|
222
|
+
ui_hash['0'] = ui_response('question_id' => '42', 'answer_id' => answer_id.to_s)
|
223
|
+
ui_hash['1'] = { 'answer_id' => '7' } # no api_id
|
224
|
+
|
225
|
+
begin
|
226
|
+
do_ui_update
|
227
|
+
fail "Expected error did not occur"
|
228
|
+
rescue
|
229
|
+
end
|
230
|
+
|
231
|
+
response_set.reload.responses.should be_empty
|
232
|
+
end
|
204
233
|
end
|
205
234
|
end
|
206
235
|
|
@@ -423,3 +452,24 @@ describe ResponseSet, "exporting csv" do
|
|
423
452
|
csv.should match /pecan pie/
|
424
453
|
end
|
425
454
|
end
|
455
|
+
|
456
|
+
describe ResponseSet, "#as_json" do
|
457
|
+
let(:rs) {
|
458
|
+
Factory(:response_set, :responses => [
|
459
|
+
Factory(:response, :question => Factory(:question), :answer => Factory(:answer), :string_value => '2')])
|
460
|
+
}
|
461
|
+
|
462
|
+
let(:js) {rs.as_json}
|
463
|
+
|
464
|
+
it "should include uuid, survey_id" do
|
465
|
+
js[:uuid].should == rs.api_id
|
466
|
+
end
|
467
|
+
|
468
|
+
it "should include responses with uuid, question_id, answer_id, value" do
|
469
|
+
r0 = rs.responses[0]
|
470
|
+
js[:responses][0][:uuid].should == r0.api_id
|
471
|
+
js[:responses][0][:answer_id].should == r0.answer.api_id
|
472
|
+
js[:responses][0][:question_id].should == r0.question.api_id
|
473
|
+
js[:responses][0][:value].should == r0.string_value
|
474
|
+
end
|
475
|
+
end
|
@@ -39,6 +39,19 @@ describe Response, "when saving a response" do
|
|
39
39
|
response2 = Factory(:response, :question => Factory(:question), :answer => Factory(:answer), :response_set => @response.response_set, :created_at => (@response.created_at + 1))
|
40
40
|
Response.all.should == [@response, response2]
|
41
41
|
end
|
42
|
+
|
43
|
+
it "should protect api_id, timestamps" do
|
44
|
+
saved_attrs = @response.attributes
|
45
|
+
if defined? ActiveModel::MassAssignmentSecurity::Error
|
46
|
+
lambda {@response.update_attributes(:created_at => 3.days.ago, :updated_at => 3.hours.ago)}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
47
|
+
lambda {@response.update_attributes(:api_id => "NEW")}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
48
|
+
else
|
49
|
+
@response.attributes = {:created_at => 3.days.ago, :updated_at => 3.hours.ago} # automatically protected by Rails
|
50
|
+
@response.attributes = {:api_id => "NEW"} # Rails doesn't return false, but this will be checked in the comparison to saved_attrs
|
51
|
+
end
|
52
|
+
@response.attributes.should == saved_attrs
|
53
|
+
end
|
54
|
+
|
42
55
|
|
43
56
|
describe "returns the response as the type requested" do
|
44
57
|
it "returns 'string'" do
|
@@ -114,3 +127,50 @@ describe Response, "applicable_attributes" do
|
|
114
127
|
should == {"question_id"=>@who.id, "answer_id"=>[""], "string_value"=>"Frank"}
|
115
128
|
end
|
116
129
|
end
|
130
|
+
|
131
|
+
describe Response, '#json_value' do
|
132
|
+
context "when integer" do
|
133
|
+
let(:r) {Response.new(:integer_value => 2, :answer => Answer.new(:response_class => 'integer'))}
|
134
|
+
it "should be 2" do
|
135
|
+
r.json_value.should == 2
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "when float" do
|
140
|
+
let(:r) {Response.new(:float_value => 3.14, :answer => Answer.new(:response_class => 'float'))}
|
141
|
+
it "should be 3.14" do
|
142
|
+
r.json_value.should == 3.14
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context "when string" do
|
147
|
+
let(:r) {Response.new(:string_value => 'bar', :answer => Answer.new(:response_class => 'string'))}
|
148
|
+
it "should be 'bar'" do
|
149
|
+
r.json_value.should == 'bar'
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context "when datetime" do
|
154
|
+
let(:r) {Response.new(:datetime_value => DateTime.strptime('2010-04-08T10:30+00:00', '%Y-%m-%dT%H:%M%z'), :answer => Answer.new(:response_class => 'datetime'))}
|
155
|
+
it "should be '2010-04-08T10:30+00:00'" do
|
156
|
+
r.json_value.should == '2010-04-08T10:30+00:00'
|
157
|
+
r.json_value.to_json.should == '"2010-04-08T10:30+00:00"'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "when date" do
|
162
|
+
let(:r) {Response.new(:datetime_value => DateTime.strptime('2010-04-08', '%Y-%m-%d'), :answer => Answer.new(:response_class => 'date'))}
|
163
|
+
it "should be '2010-04-08'" do
|
164
|
+
r.json_value.should == '2010-04-08'
|
165
|
+
r.json_value.to_json.should == '"2010-04-08"'
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context "when time" do
|
170
|
+
let(:r) {Response.new(:datetime_value => DateTime.strptime('10:30', '%H:%M'), :answer => Answer.new(:response_class => 'time'))}
|
171
|
+
it "should be '10:30'" do
|
172
|
+
r.json_value.should == '10:30'
|
173
|
+
r.json_value.to_json.should == '"10:30"'
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -16,6 +16,15 @@ describe SurveySection, "when saving a survey_section" do
|
|
16
16
|
# @survey_section.survey_id = nil
|
17
17
|
# @survey_section.should have(1).error_on(:survey)
|
18
18
|
end
|
19
|
+
it "should protect timestamps" do
|
20
|
+
saved_attrs = @survey_section.attributes
|
21
|
+
if defined? ActiveModel::MassAssignmentSecurity::Error
|
22
|
+
lambda {@survey_section.update_attributes(:created_at => 3.days.ago, :updated_at => 3.hours.ago)}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
23
|
+
else
|
24
|
+
@survey_section.attributes = {:created_at => 3.days.ago, :updated_at => 3.hours.ago} # automatically protected by Rails
|
25
|
+
end
|
26
|
+
@survey_section.attributes.should == saved_attrs
|
27
|
+
end
|
19
28
|
end
|
20
29
|
|
21
30
|
describe SurveySection, "with questions" do
|
data/spec/models/survey_spec.rb
CHANGED
@@ -11,15 +11,28 @@ describe Survey, "when saving a new one" do
|
|
11
11
|
@survey.should have(1).error_on(:title)
|
12
12
|
end
|
13
13
|
|
14
|
-
it "should adjust the
|
14
|
+
it "should adjust the survey_version to save unique survey_version for each title" do
|
15
15
|
original = Survey.new(:title => "Foo")
|
16
16
|
original.save.should be_true
|
17
|
+
original.survey_version.should == 0
|
17
18
|
imposter = Survey.new(:title => "Foo")
|
18
19
|
imposter.save.should be_true
|
19
|
-
imposter.title.should == "Foo
|
20
|
+
imposter.title.should == "Foo"
|
21
|
+
imposter.survey_version.should == 1
|
20
22
|
bandwagoneer = Survey.new(:title => "Foo")
|
21
23
|
bandwagoneer.save.should be_true
|
22
|
-
bandwagoneer.title.should == "Foo
|
24
|
+
bandwagoneer.title.should == "Foo"
|
25
|
+
bandwagoneer.survey_version.should == 2
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should not allow to have duplicate survey_versions of the survey" do
|
29
|
+
survey = Survey.new(:title => "Foo")
|
30
|
+
survey.save.should be_true
|
31
|
+
imposter = Survey.new(:title => "Foo")
|
32
|
+
imposter.save.should be_true
|
33
|
+
imposter.survey_version = 0
|
34
|
+
imposter.save.should be_false
|
35
|
+
imposter.should have(1).error_on(:survey_version)
|
23
36
|
end
|
24
37
|
|
25
38
|
it "should not adjust the title when updating itself" do
|
@@ -72,19 +85,21 @@ describe Survey do
|
|
72
85
|
it "should be inactive by default" do
|
73
86
|
@survey.active?.should == false
|
74
87
|
end
|
75
|
-
|
88
|
+
it "should have both inactive_at and active_at be null by default" do
|
89
|
+
@survey.active_at.should be_nil
|
90
|
+
@survey.inactive_at.should be_nil
|
91
|
+
end
|
92
|
+
|
76
93
|
it "should be active or active as of a certain date/time" do
|
77
|
-
@survey.inactive_at =
|
94
|
+
@survey.inactive_at = 2.days.from_now
|
78
95
|
@survey.active_at = 2.days.ago
|
79
96
|
@survey.active?.should be_true
|
80
|
-
@survey.inactive_at.should be_nil
|
81
97
|
end
|
82
98
|
|
83
99
|
it "should be able to deactivate as of a certain date/time" do
|
84
|
-
@survey.active_at =
|
85
|
-
@survey.inactive_at =
|
100
|
+
@survey.active_at = 3.days.ago
|
101
|
+
@survey.inactive_at = 1.days.ago
|
86
102
|
@survey.active?.should be_false
|
87
|
-
@survey.active_at.should be_nil
|
88
103
|
end
|
89
104
|
|
90
105
|
it "should activate and deactivate" do
|
@@ -94,4 +109,52 @@ describe Survey do
|
|
94
109
|
@survey.active?.should be_false
|
95
110
|
end
|
96
111
|
|
112
|
+
it "should should nil out values of inactive_at that are in the past on activate" do
|
113
|
+
@survey.inactive_at = 5.days.ago
|
114
|
+
@survey.active?.should be_false
|
115
|
+
@survey.activate!
|
116
|
+
@survey.active?.should be_true
|
117
|
+
@survey.inactive_at.should be_nil
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should should nil out values of active_at that are in the past on deactivate" do
|
121
|
+
@survey.active_at = 5.days.ago
|
122
|
+
@survey.active?.should be_true
|
123
|
+
@survey.deactivate!
|
124
|
+
@survey.active?.should be_false
|
125
|
+
@survey.active_at.should be_nil
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should protect access_code, api_id, active_at, inactive_at, timestamps" do
|
129
|
+
saved_attrs = @survey.attributes
|
130
|
+
if defined? ActiveModel::MassAssignmentSecurity::Error
|
131
|
+
lambda {@survey.update_attributes(:access_code => "NEW")}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
132
|
+
lambda {@survey.update_attributes(:api_id => "AND")}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
133
|
+
lambda {@survey.update_attributes(:active_at => 2.days.ago)}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
134
|
+
lambda {@survey.update_attributes(:inactive_at => 3.days.from_now)}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
135
|
+
lambda {@survey.update_attributes(:created_at => 3.days.ago, :updated_at => 3.hours.ago)}.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
136
|
+
else
|
137
|
+
@survey.update_attributes(:access_code => "NEW").should be_false
|
138
|
+
@survey.update_attributes(:api_id => "AND").should be_false
|
139
|
+
@survey.update_attributes(:active_at => 2.days.ago).should be_false
|
140
|
+
@survey.update_attributes(:inactive_at => 3.days.from_now).should be_false
|
141
|
+
@survey.attributes = {:created_at => 3.days.ago, :updated_at => 3.hours.ago} # automatically protected by Rails
|
142
|
+
end
|
143
|
+
@survey.attributes.should == saved_attrs
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should include title, sections, and questions when serialized" do
|
147
|
+
survey = Factory(:survey, :title => "Foo")
|
148
|
+
s1 = Factory(:survey_section, :survey => survey, :title => "wise")
|
149
|
+
s2 = Factory(:survey_section, :survey => survey, :title => "er")
|
150
|
+
q1 = Factory(:question, :survey_section => s1, :text => "what is wise?")
|
151
|
+
q2 = Factory(:question, :survey_section => s2, :text => "what is er?")
|
152
|
+
q3 = Factory(:question, :survey_section => s2, :text => "what is mill?")
|
153
|
+
|
154
|
+
actual = survey.as_json
|
155
|
+
actual[:title].should == 'Foo'
|
156
|
+
actual[:sections].size.should == 2
|
157
|
+
actual[:sections][0][:questions_and_groups].size.should == 1
|
158
|
+
actual[:sections][1][:questions_and_groups].size.should == 2
|
159
|
+
end
|
97
160
|
end
|