quby 5.0.0 → 5.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.markdown +11 -2
  3. data/app/assets/javascripts/quby/answers/validation.js +3 -1
  4. data/lib/quby/answers/services/answer_validator.rb +44 -0
  5. data/lib/quby/questionnaires/deserializer.rb +10 -5
  6. data/lib/quby/questionnaires/entities/charting/bar_chart.rb +1 -5
  7. data/lib/quby/questionnaires/entities/charting/chart.rb +5 -1
  8. data/lib/quby/questionnaires/entities/charting/radar_chart.rb +1 -5
  9. data/lib/quby/questionnaires/specs/api_specs.rb +7 -5
  10. data/lib/quby/version.rb +1 -1
  11. data/spec/features/charts_spec.rb +9 -2
  12. data/spec/features/editing_completed_answer_spec.rb +2 -2
  13. data/spec/features/question_dependencies/group_maximum_answered_spec.rb +65 -2
  14. data/spec/features/question_dependencies/group_minimum_answered_spec.rb +42 -2
  15. data/spec/features/question_dependencies/group_minmax_answered_spec.rb +44 -7
  16. data/spec/fixtures/questionnaire_with_chart.rb +1 -0
  17. data/spec/fixtures/scores_from_schema.rb +25 -0
  18. data/spec/quby/answers/entities/score_spec.rb +51 -44
  19. data/spec/quby/answers/services/answer_validator_spec.rb +50 -9
  20. data/spec/quby/questionnaires/entities/charting/bar_chart_spec.rb +2 -8
  21. data/spec/quby/questionnaires/entities/charting/radar_chart_spec.rb +0 -5
  22. data/spec/quby/questionnaires/entities/fields_spec.rb +2 -2
  23. data/spec/quby/questionnaires/entities/questionnaire_spec.rb +4 -2
  24. data/spec/quby/questionnaires/repos/disk_repo_spec.rb +3 -1
  25. data/spec/quby/questionnaires/repos/memory_repo_spec.rb +2 -1
  26. data/spec/spec_helper.rb +13 -2
  27. data/spec/support/examples_for_chart.rb +10 -0
  28. data/spec/support/validation_helpers.rb +9 -0
  29. data/spec/teaspoon_env.rb +32 -2
  30. metadata +9 -7
@@ -15,6 +15,7 @@ shared_examples 'group_minimum_answered_tests' do
15
15
  fill_in_question('v_date_year', '2013')
16
16
  fill_in_question('v_date_month', '12')
17
17
  fill_in_question('v_date_day', '10')
18
+ set_slider_value('v_slider', '50')
18
19
  run_validations
19
20
  expect_no_errors
20
21
  expect_saved_value 'v_radio', 'a1'
@@ -27,6 +28,7 @@ shared_examples 'group_minimum_answered_tests' do
27
28
  expect_saved_value 'v_date_year', "2013"
28
29
  expect_saved_value 'v_date_month', "12"
29
30
  expect_saved_value 'v_date_day', "10"
31
+ # expect_saved_value 'v_slider', '50' # occasional client side rounding issue :S
30
32
  end
31
33
 
32
34
  it 'is not valid when radio question is not filled' do
@@ -41,6 +43,7 @@ shared_examples 'group_minimum_answered_tests' do
41
43
  fill_in_question('v_date_year', '2013')
42
44
  fill_in_question('v_date_month', '12')
43
45
  fill_in_question('v_date_day', '10')
46
+ set_slider_value('v_slider', '50')
44
47
  run_validations
45
48
  expect_errors_on_group
46
49
  end
@@ -57,6 +60,7 @@ shared_examples 'group_minimum_answered_tests' do
57
60
  fill_in_question('v_date_year', '2013')
58
61
  fill_in_question('v_date_month', '12')
59
62
  fill_in_question('v_date_day', '10')
63
+ set_slider_value('v_slider', '50')
60
64
  run_validations
61
65
  expect_errors_on_group
62
66
  end
@@ -73,6 +77,7 @@ shared_examples 'group_minimum_answered_tests' do
73
77
  fill_in_question('v_date_year', '2013')
74
78
  fill_in_question('v_date_month', '12')
75
79
  fill_in_question('v_date_day', '10')
80
+ set_slider_value('v_slider', '50')
76
81
  run_validations
77
82
  expect_errors_on_group
78
83
  end
@@ -89,6 +94,7 @@ shared_examples 'group_minimum_answered_tests' do
89
94
  fill_in_question('v_date_year', '2013')
90
95
  fill_in_question('v_date_month', '12')
91
96
  fill_in_question('v_date_day', '10')
97
+ set_slider_value('v_slider', '50')
92
98
  run_validations
93
99
  expect_errors_on_group
94
100
  end
@@ -105,6 +111,7 @@ shared_examples 'group_minimum_answered_tests' do
105
111
  fill_in_question('v_date_year', '2013')
106
112
  fill_in_question('v_date_month', '12')
107
113
  fill_in_question('v_date_day', '10')
114
+ set_slider_value('v_slider', '50')
108
115
  run_validations
109
116
  expect_errors_on_group
110
117
  end
@@ -121,6 +128,7 @@ shared_examples 'group_minimum_answered_tests' do
121
128
  fill_in_question('v_date_year', '2013')
122
129
  fill_in_question('v_date_month', '12')
123
130
  fill_in_question('v_date_day', '10')
131
+ set_slider_value('v_slider', '50')
124
132
  run_validations
125
133
  expect_errors_on_group
126
134
  end
@@ -137,6 +145,7 @@ shared_examples 'group_minimum_answered_tests' do
137
145
  fill_in_question('v_date_year', '2013')
138
146
  fill_in_question('v_date_month', '12')
139
147
  fill_in_question('v_date_day', '10')
148
+ set_slider_value('v_slider', '50')
140
149
  run_validations
141
150
  expect_errors_on_group
142
151
  end
@@ -153,6 +162,24 @@ shared_examples 'group_minimum_answered_tests' do
153
162
  # fill_in_question('v_date_year', '2013')
154
163
  # fill_in_question('v_date_month', '12')
155
164
  # fill_in_question('v_date_day', '10')
165
+ set_slider_value('v_slider', '50')
166
+ run_validations
167
+ expect_errors_on_group
168
+ end
169
+
170
+ it 'is not valid when slider question is not filled' do
171
+ select_radio_option 'v_radio', 'a1'
172
+ select_radio_option 'v_scale', 'a1'
173
+ select_select_option 'v_select', 'a1'
174
+ check_option 'v_ck_a1'
175
+ uncheck_option 'v_ck_a2'
176
+ fill_in_question 'v_string', 'kittens!'
177
+ fill_in_question 'v_integer', '37'
178
+ fill_in_question 'v_float', '4.2'
179
+ fill_in_question('v_date_year', '2013')
180
+ fill_in_question('v_date_month', '12')
181
+ fill_in_question('v_date_day', '10')
182
+ # set_slider_value('v_slider', '50')
156
183
  run_validations
157
184
  expect_errors_on_group
158
185
  end
@@ -166,7 +193,7 @@ shared_examples 'group_minimum_answered' do
166
193
  context 'minimum number of given answers in a group' do
167
194
  let(:questionnaire) do
168
195
  inject_questionnaire 'test', <<-END
169
- n = 8
196
+ n = 9
170
197
 
171
198
  panel do
172
199
  question :v_radio, type: :radio, question_group: :group1, group_minimum_answered: n do
@@ -210,6 +237,12 @@ shared_examples 'group_minimum_answered' do
210
237
  year_key: :v_date_year, month_key: :v_date_month, day_key: :v_date_day do
211
238
  title "Enter a date"
212
239
  end
240
+
241
+ question :v_slider, type: :float, as: :slider, size: 20, default_position: :hidden,
242
+ question_group: :group1, group_minimum_answered: n do
243
+ title "Slider"
244
+ validates_in_range 0..100
245
+ end
213
246
  end; end_panel
214
247
  END
215
248
  end
@@ -220,7 +253,7 @@ shared_examples 'group_minimum_answered' do
220
253
  context 'table-based minimum number of given answers in a group' do
221
254
  let(:questionnaire) do
222
255
  inject_questionnaire 'test', <<-END
223
- n = 8
256
+ n = 9
224
257
 
225
258
  panel do
226
259
  table columns: 20 do
@@ -266,6 +299,12 @@ shared_examples 'group_minimum_answered' do
266
299
  title "Enter a date"
267
300
  end
268
301
  end
302
+
303
+ question :v_slider, type: :float, as: :slider, size: 20, default_position: :hidden,
304
+ question_group: :group1, group_minimum_answered: n do
305
+ title "Slider"
306
+ validates_in_range 0..100
307
+ end
269
308
  end
270
309
  end_panel
271
310
  END
@@ -352,6 +391,7 @@ shared_examples 'group_minimum_answered' do
352
391
  expect_error_on 'v_integer', 'answer_group_minimum'
353
392
  expect_error_on 'v_float', 'answer_group_minimum'
354
393
  expect_error_on 'v_date', 'answer_group_minimum'
394
+ expect_error_on 'v_slider', 'answer_group_minimum'
355
395
  end
356
396
  end
357
397
 
@@ -3,35 +3,58 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  shared_examples 'group_minmax_answered_tests' do
6
- it 'is valid when first question is filled in' do
6
+ it 'is valid when radio question is not filled in' do
7
+ # select_radio_option 'v_radio', 'a1'
8
+ select_radio_option 'v_scale', 'a1'
9
+ set_slider_value 'v_slider', '50'
10
+ run_validations
11
+ expect_no_errors
12
+ # expect_saved_value 'v_radio', 'a1'
13
+ expect_saved_value 'v_scale', 'a1'
14
+ end
15
+
16
+ it 'is valid when scale question is not filled in' do
7
17
  select_radio_option 'v_radio', 'a1'
8
18
  # select_radio_option 'v_scale', 'a1'
19
+ set_slider_value 'v_slider', '50'
9
20
  run_validations
10
21
  expect_no_errors
11
22
  expect_saved_value 'v_radio', 'a1'
12
23
  # expect_saved_value 'v_scale', 'a1'
13
24
  end
14
25
 
15
- it 'is valid when second question is filled in' do
16
- # select_radio_option 'v_radio', 'a1'
26
+ it 'is valid when slider question is not filled in' do
27
+ select_radio_option 'v_radio', 'a1'
17
28
  select_radio_option 'v_scale', 'a1'
29
+ # set_slider_value 'v_slider', '50'
18
30
  run_validations
19
31
  expect_no_errors
20
- # expect_saved_value 'v_radio', 'a1'
32
+ expect_saved_value 'v_radio', 'a1'
21
33
  expect_saved_value 'v_scale', 'a1'
22
34
  end
23
35
 
24
36
  it 'is not valid when no questions are filled' do
25
37
  # select_radio_option 'v_radio', 'a1'
26
38
  # select_radio_option 'v_scale', 'a1'
39
+ # set_slider_value 'v_slider', '50'
27
40
  run_validations
28
41
  expect_error_on 'v_radio', 'answer_group_minimum'
29
42
  expect_error_on 'v_scale', 'answer_group_minimum'
30
43
  end
31
44
 
32
- it 'is not valid when both questions are filled' do
45
+ it 'is not valid when only one question is filled' do
46
+ select_radio_option 'v_radio', 'a1'
47
+ # select_radio_option 'v_scale', 'a1'
48
+ # set_slider_value 'v_slider', '50'
49
+ run_validations
50
+ expect_error_on 'v_radio', 'answer_group_minimum'
51
+ expect_error_on 'v_scale', 'answer_group_minimum'
52
+ end
53
+
54
+ it 'is not valid when all questions are filled' do
33
55
  select_radio_option 'v_radio', 'a1'
34
56
  select_radio_option 'v_scale', 'a1'
57
+ set_slider_value 'v_slider', '50'
35
58
  run_validations
36
59
  expect_error_on 'v_radio', 'answer_group_maximum'
37
60
  expect_error_on 'v_scale', 'answer_group_maximum'
@@ -45,7 +68,7 @@ shared_examples 'group_minmax_answered' do
45
68
  context 'normal' do
46
69
  let(:questionnaire) do
47
70
  inject_questionnaire 'test', <<-END
48
- n = 1
71
+ n = 2
49
72
 
50
73
  panel do
51
74
  question :v_radio, type: :radio, question_group: :group1, group_minimum_answered: n,
@@ -61,6 +84,13 @@ shared_examples 'group_minmax_answered' do
61
84
  option :a1, value: 1, description: "Scale Option 1"
62
85
  option :a2, value: 2, description: "Scale Option 2"
63
86
  end
87
+
88
+ question :v_slider, type: :float, as: :slider, size: 20, default_position: :hidden,
89
+ question_group: :group1, group_minimum_answered: n,
90
+ group_maximum_answered: n do
91
+ title "Slider"
92
+ validates_in_range 0..100
93
+ end
64
94
  end; end_panel
65
95
  END
66
96
  end
@@ -71,7 +101,7 @@ shared_examples 'group_minmax_answered' do
71
101
  context 'table-based' do
72
102
  let(:questionnaire) do
73
103
  inject_questionnaire 'test', <<-END
74
- n = 1
104
+ n = 2
75
105
 
76
106
  panel do
77
107
  table columns: 20 do
@@ -89,6 +119,13 @@ shared_examples 'group_minmax_answered' do
89
119
  option :a2, value: 2, description: "Scale Option 2"
90
120
  end
91
121
  end
122
+
123
+ question :v_slider, type: :float, as: :slider, size: 20, default_position: :hidden,
124
+ question_group: :group1, group_minimum_answered: n,
125
+ group_maximum_answered: n do
126
+ title "Slider"
127
+ validates_in_range 0..100
128
+ end
92
129
  end
93
130
  end_panel
94
131
  END
@@ -33,4 +33,5 @@ line_chart :tot do
33
33
  plot :v_3
34
34
  plotband 30, 50, :yellow
35
35
  plotband 50, 80, :red
36
+ plotline 20, :green
36
37
  end
@@ -0,0 +1,25 @@
1
+ title 'Score test'
2
+
3
+ question :v_1, type: :integer
4
+
5
+ score_schema :test, 'Testscore' do
6
+ subscore :value, 'Waarde', export_key: :tes do
7
+ value(:v_1)
8
+ end
9
+ subscore :interpretation, 'Waarde', export_key: :tes_i do
10
+ 'Matig'
11
+ end
12
+ end
13
+
14
+ score_schema :test2, 'Testscore 2' do
15
+ subscore :value, 'Waarde', export_key: :tes2 do
16
+ values_with_nils(:v_1).first + 5
17
+ end
18
+ subscore :interpretation, 'Waarde', export_key: :tes2_i do
19
+ 'Miniem'
20
+ end
21
+ end
22
+
23
+ completion do
24
+ values_with_nils(:v_1).compact.size / 1.0
25
+ end
@@ -4,15 +4,10 @@ require 'spec_helper'
4
4
 
5
5
  module Quby::Answers::Entities
6
6
  describe Score do
7
- let(:questionnaire) do
8
- Quby.questionnaires.find('score_test')
9
- end
10
-
11
7
  let(:v_1_value) { 10 }
12
-
13
8
  let(:answer) do
14
9
  # complicated way to get an answer
15
- answer = Quby.answers.create!('score_test')
10
+ answer = Quby.answers.create!(questionnaire_key)
16
11
  answer.value = {'v_1' => v_1_value}
17
12
  Quby.answers.update!(answer)
18
13
  Quby.answers.regenerate_outcome!(answer)
@@ -22,56 +17,68 @@ module Quby::Answers::Entities
22
17
  # it works with indifferent access
23
18
  subject { answer.score_objects['test'] }
24
19
 
25
- it 'has working completion' do
26
- expect(answer.outcome.completion).to eq("value" => 1.0)
27
- end
28
-
29
- it 'exposes score schema fields' do
30
- expect(subject.key).to eq(:test)
31
- expect(subject.label).to eq('Testscore')
32
- end
33
-
34
- it 'exposes #referenced_values' do
35
- expect(subject.referenced_values).to eq(["v_1"])
36
- end
20
+ shared_examples 'score test' do
21
+ it 'has working completion' do
22
+ expect(answer.outcome.completion).to eq("value" => 1.0)
23
+ end
37
24
 
38
- describe '#[] to access subscores' do
39
- let(:subscore) { subject[:value] }
40
- it 'exposes subscore schema fields' do
41
- expect(subscore.key).to eq(:value)
42
- expect(subscore.export_key).to eq(:tes)
43
- expect(subscore.only_for_export).to eq(nil)
44
- expect(subscore.label).to eq('Waarde')
25
+ it 'exposes score schema fields' do
26
+ expect(subject.key).to eq(:test)
27
+ expect(subject.label).to eq('Testscore')
45
28
  end
46
29
 
47
- it 'exposes subscore values, with indifferent access' do
48
- expect(subscore.value).to eq(10)
49
- expect(subject['interpretation'].value).to eq('Matig')
30
+ it 'exposes #referenced_values' do
31
+ expect(subject.referenced_values).to eq(["v_1"])
50
32
  end
51
- end
52
33
 
53
- describe 'when the score has missing values' do
54
- let(:v_1_value) { nil }
55
- it 'exposes nil as the value for each subscore, and leaves schema information alone' do
56
- expect(subject[:interpretation].value).to eq(nil)
57
- expect(subject[:value].export_key).to eq(:tes)
58
- expect(subject.key).to eq(:test)
34
+ describe '#[] to access subscores' do
35
+ let(:subscore) { subject[:value] }
36
+ it 'exposes subscore schema fields' do
37
+ expect(subscore.key).to eq(:value)
38
+ expect(subscore.export_key).to eq(:tes)
39
+ expect(subscore.only_for_export).to eq(nil)
40
+ expect(subscore.label).to eq('Waarde')
41
+ end
42
+
43
+ it 'exposes subscore values, with indifferent access' do
44
+ expect(subscore.value).to eq(10)
45
+ expect(subject['interpretation'].value).to eq('Matig')
46
+ end
59
47
  end
60
48
 
61
- describe 'when the score has an exception' do
62
- # second score will error on nil
63
- subject { answer.score_objects[:test2] }
49
+ describe 'when the score has missing values' do
50
+ let(:v_1_value) { nil }
64
51
  it 'exposes nil as the value for each subscore, and leaves schema information alone' do
65
- expect(subject[:value].value).to eq(nil)
66
- expect(subject[:value].export_key).to eq(:tes2)
67
- expect(subject.key).to eq(:test2)
52
+ expect(subject[:interpretation].value).to eq(nil)
53
+ expect(subject[:value].export_key).to eq(:tes)
54
+ expect(subject.key).to eq(:test)
68
55
  end
69
56
 
70
- it 'exposes the exception and backtrace under #error' do
71
- expect(subject.error).to match({exception: "undefined method `+' for nil:NilClass",
72
- backtrace: an_instance_of(Array)})
57
+ describe 'when the score has an exception' do
58
+ # second score will error on nil
59
+ subject { answer.score_objects[:test2] }
60
+ it 'exposes nil as the value for each subscore, and leaves schema information alone' do
61
+ expect(subject[:value].value).to eq(nil)
62
+ expect(subject[:value].export_key).to eq(:tes2)
63
+ expect(subject.key).to eq(:test2)
64
+ end
65
+
66
+ it 'exposes the exception and backtrace under #error' do
67
+ expect(subject.error).to match({exception: "undefined method `+' for nil:NilClass",
68
+ backtrace: an_instance_of(Array)})
69
+ end
73
70
  end
74
71
  end
75
72
  end
73
+
74
+ describe 'with explicit score calculations' do
75
+ let(:questionnaire_key) {'score_test'}
76
+ it_behaves_like 'score test'
77
+ end
78
+
79
+ describe 'with score schema based score calculations' do
80
+ let(:questionnaire_key) {'scores_from_schema'}
81
+ it_behaves_like 'score test'
82
+ end
76
83
  end
77
84
  end
@@ -4,7 +4,7 @@ require 'spec_helper'
4
4
 
5
5
  module Quby::Answers::Services
6
6
  describe AnswerValidator do
7
- let(:questionnaire) do
7
+ let(:default_questionnaire) do
8
8
  inject_questionnaire "test", <<-END
9
9
  panel do
10
10
  question :v_check_box, type: :check_box do
@@ -24,7 +24,7 @@ module Quby::Answers::Services
24
24
  END
25
25
  end
26
26
 
27
- let(:answer_value) do
27
+ let(:default_answer_value) do
28
28
  { v_check_box: { v_ck_a1: 1, v_ck_a2: 0 },
29
29
  v_ck_a1: 1,
30
30
  v_ck_a2: 0,
@@ -34,12 +34,16 @@ module Quby::Answers::Services
34
34
  }
35
35
  end
36
36
 
37
- let!(:answer) do
38
- answer = Quby.answers.create!(questionnaire.key, value: answer_value, flags: {}, textvars: {})
37
+ def build_answer(questionnaire: default_questionnaire, value: default_answer_value)
38
+ answer = Quby.answers.create!(questionnaire.key, value: value, flags: {}, textvars: {})
39
39
  answer.extend AnswerValidations
40
+ answer
40
41
  end
41
42
 
42
- let(:validator) { described_class.new(questionnaire, answer) }
43
+
44
+ def validator(questionnaire: default_questionnaire, answer: build_answer)
45
+ described_class.new(questionnaire, answer)
46
+ end
43
47
 
44
48
  describe '#depends_on_key_answered' do
45
49
  it 'returns true if a check box option is answered' do
@@ -64,17 +68,54 @@ module Quby::Answers::Services
64
68
 
65
69
  describe '#validate' do
66
70
  it 'sets validation errors on the answer' do
67
- validator.validate
71
+ answer = build_answer
72
+ validator(answer: answer).validate
68
73
  expect(answer.errors).to_not be_empty
69
74
  end
70
75
 
71
76
  context 'when answer is aborted' do
72
- before do
77
+ it 'skips the validate_required validation' do
78
+ answer = build_answer
73
79
  allow(answer).to receive(:aborted).and_return(true)
80
+ validator(answer: answer).validate
81
+ expect(answer.errors).to be_empty
74
82
  end
75
83
 
76
- it 'skips the validate_required validation' do
77
- validator.validate
84
+ it 'skips the validate_minimum_checked_required validation' do
85
+ questionnaire = inject_questionnaire "test", <<-END
86
+ question :v_ck, type: :check_box, required: true, minimum_checked_required: 2 do
87
+ option :v_ck_a1, value: 1
88
+ option :v_ck_a2, value: 2
89
+ option :v_ck_a3, value: 3
90
+ end
91
+ END
92
+
93
+ answer = build_answer(questionnaire: questionnaire, value: {v_ck: {v_ck_a1: 0, v_ck_a2: 0, v_ck_a3: 1}})
94
+ validator(questionnaire: questionnaire, answer: answer).validate
95
+ expect(answer.errors[:v_ck]).to include(message: "Not enough options checked.", valtype: :minimum_checked_required)
96
+
97
+ answer = build_answer(questionnaire: questionnaire, value: {v_ck: {v_ck_a1: 0, v_ck_a2: 0, v_ck_a3: 1}})
98
+ allow(answer).to receive(:aborted).and_return(true)
99
+ validator(questionnaire: questionnaire, answer: answer).validate
100
+ expect(answer.errors).to be_empty
101
+ end
102
+
103
+ it 'skips the validate_answer_group_minimum validation' do
104
+ questionnaire = inject_questionnaire "test", <<-END
105
+ question :v_1, type: :string, question_group: :grp, group_minimum_answered: 2
106
+ question :v_2, type: :string, question_group: :grp, group_minimum_answered: 2
107
+ question :v_3, type: :string, question_group: :grp, group_minimum_answered: 2
108
+ END
109
+
110
+ answer = build_answer(questionnaire: questionnaire, value: {v_1: nil, v_2: nil, v_3: 'bla'})
111
+ validator(questionnaire: questionnaire, answer: answer).validate
112
+ expect(answer.errors[:v_1]).to include(message: "Needs at least 2 question(s) answered.", valtype: :answer_group_minimum)
113
+ expect(answer.errors[:v_2]).to include(message: "Needs at least 2 question(s) answered.", valtype: :answer_group_minimum)
114
+ expect(answer.errors[:v_3]).to include(message: "Needs at least 2 question(s) answered.", valtype: :answer_group_minimum)
115
+
116
+ answer = build_answer(questionnaire: questionnaire, value: {v_1: nil, v_2: nil, v_3: 'bla'})
117
+ allow(answer).to receive(:aborted).and_return(true)
118
+ validator(questionnaire: questionnaire, answer: answer).validate
78
119
  expect(answer.errors).to be_empty
79
120
  end
80
121
  end